官方不支持将仿函数公开为方法。支持的方法是公开一个委托给成员函数的非成员函数。但是,这可能会导致大量样板代码。
据我所知,Boost.Python 的实现并没有明确排除仿函数,因为它允许将 的实例python::object
作为方法公开。但是,Boost.Python 确实对作为方法公开的对象类型提出了一些要求:
- 函子是 CopyConstructible 的。
- 函子是可调用的。
o
即可以调用实例o(a1, a2, a3)
。
- 调用签名必须在运行时作为元数据可用。Boost.Python 调用该
boost::python::detail::get_signature()
函数来获取此元数据。元数据在内部用于设置正确的调用,以及从 Python 到 C++ 的调度。
后一个要求是它变得复杂的地方。出于某种我不清楚的原因,Boost.Pythonget_signature()
通过限定 ID 进行调用,从而阻止了参数相关的查找。get_signature()
因此,必须在调用模板的定义上下文之前声明所有候选对象。例如,唯一get_signature()
考虑的重载是在调用它的模板定义之前声明的重载,例如class_
、def()
和make_function()
。为了解释这种行为,在 Boost.Python 中启用仿函数时,必须get_signature()
在包含 Boost.Python 之前提供重载,或者显式提供表示签名的元序列make_function()
。
让我们通过一些启用仿函数支持的示例,以及提供支持守卫的仿函数。我选择不使用 C++11 特性。因此,将会有一些样板代码可以通过可变参数模板减少。此外,所有示例都将使用相同的模型,该模型提供两个非成员函数和一个spam
具有两个成员函数的类:
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
启用boost::function
当使用 Boost.Function 的首选语法时,可以使用Boost.FunctionTypes将签名分解为满足 Boost.Python 要求的元数据。这是一个完整的示例,使boost::function
函子可以作为 Boost.Python 方法公开:
#include <iostream>
#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
namespace boost {
namespace python {
namespace detail {
// get_signature overloads must be declared before including
// boost/python.hpp. The declaration must be visible at the
// point of definition of various Boost.Python templates during
// the first phase of two phase lookup. Boost.Python invokes the
// get_signature function via qualified-id, thus ADL is disabled.
/// @brief Get the signature of a boost::function.
template <typename Signature>
inline typename boost::function_types::components<Signature>::type
get_signature(boost::function<Signature>&, void* = 0)
{
return typename boost::function_types::components<Signature>::type();
}
} // namespace detail
} // namespace python
} // namespace boost
#include <boost/python.hpp>
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose class and member-function.
python::class_<spam>("Spam")
.def("action", &spam::action)
.def("times_two", boost::function<int(spam&, int)>(
&spam::times_two))
;
// Expose non-member function.
python::def("action", &action);
python::def("times_two", boost::function<int()>(
boost::bind(×_two, 21)));
}
及其用法:
>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
spam::times_two()
10
>>> example.action()
action()
>>> example.times_two()
times_two()
42
当提供将调用成员函数的仿函数时,提供的签名需要是等效的非成员函数。在这种情况下,int(spam::*)(int)
变为int(spam&, int)
。
// ...
.def("times_two", boost::function<int(spam&, int)>(
&spam::times_two))
;
此外,参数可以绑定到具有 的函子boost::bind
。例如,调用example.times_two()
不必提供参数,因为21
它已经绑定到函子。
python::def("times_two", boost::function<int()>(
boost::bind(×_two, 21)));
带警卫的自定义函子
扩展上面的示例,可以启用自定义函子类型以与 Boost.Python 一起使用。让我们创建一个名为 的仿函数guarded_function
,它将使用RAII,仅在 RAII 对象的生命周期内调用包装函数。
/// @brief Functor that will invoke a function while holding a guard.
/// Upon returning from the function, the guard is released.
template <typename Signature,
typename Guard>
class guarded_function
{
public:
typedef typename boost::function_types::result_type<Signature>::type
result_type;
template <typename Fn>
guarded_function(Fn fn)
: fn_(fn)
{}
result_type operator()()
{
Guard g;
return fn_();
}
// ... overloads for operator()
private:
boost::function<Signature> fn_;
};
提供与 Python语句guarded_function
类似的语义。with
因此,为了与 Boost.Python API 名称选择保持一致,with()
C++ 函数将提供一种创建函子的方法。
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn>
boost::python::object
with(Fn fn)
{
return boost::python::make_function(
guarded_function<Guard, Fn>(fn), ...);
}
这允许暴露功能,这些功能将以非侵入性方式与警卫一起运行:
class no_gil; // Guard
// ...
.def("times_two", with<no_gil>(&spam::times_two))
;
此外,该with()
函数提供了推断函数签名的能力,允许将元数据签名显式提供给 Boost.Python,而不必重载boost::python::detail::get_signature()
.
这是使用两种 RAII 类型的完整示例:
no_gil
: 在构造函数中释放 GIL,在析构函数中重新获取 GIL。
echo_guard
: 在构造函数和析构函数中打印。
#include <iostream>
#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/python.hpp>
#include <boost/tuple/tuple.hpp>
namespace detail {
/// @brief Functor that will invoke a function while holding a guard.
/// Upon returning from the function, the guard is released.
template <typename Signature,
typename Guard>
class guarded_function
{
public:
typedef typename boost::function_types::result_type<Signature>::type
result_type;
template <typename Fn>
guarded_function(Fn fn)
: fn_(fn)
{}
result_type operator()()
{
Guard g;
return fn_();
}
template <typename A1>
result_type operator()(A1 a1)
{
Guard g;
return fn_(a1);
}
template <typename A1, typename A2>
result_type operator()(A1 a1, A2 a2)
{
Guard g;
return fn_(a1, a2);
}
private:
boost::function<Signature> fn_;
};
/// @brief Provides signature type.
template <typename Signature>
struct mpl_signature
{
typedef typename boost::function_types::components<Signature>::type type;
};
// Support boost::function.
template <typename Signature>
struct mpl_signature<boost::function<Signature> >:
public mpl_signature<Signature>
{};
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn,
typename Policy>
boost::python::object with_aux(Fn fn, const Policy& policy)
{
// Obtain the components of the Fn. This will decompose non-member
// and member functions into an mpl sequence.
// R (*)(A1) => R, A1
// R (C::*)(A1) => R, C*, A1
typedef typename mpl_signature<Fn>::type mpl_signature_type;
// Synthesize the components into a function type. This process
// causes member functions to require the instance argument.
// This is necessary because member functions will be explicitly
// provided the 'self' argument.
// R, A1 => R (*)(A1)
// R, C*, A1 => R (*)(C*, A1)
typedef typename boost::function_types::function_type<
mpl_signature_type>::type signature_type;
// Create a callable boost::python::object that delegates to the
// guarded_function.
return boost::python::make_function(
guarded_function<signature_type, Guard>(fn),
policy, mpl_signature_type());
}
} // namespace detail
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn,
typename Policy>
boost::python::object with(const Fn& fn, const Policy& policy)
{
return detail::with_aux<Guard>(fn, policy);
}
/// @brief Create a callable object with guards.
template <typename Guard,
typename Fn>
boost::python::object with(const Fn& fn)
{
return with<Guard>(fn, boost::python::default_call_policies());
}
/// @brief Mockup class with member functions.
class spam
{
public:
void action()
{
std::cout << "spam::action()" << std::endl;
}
int times_two(int x)
{
std::cout << "spam::times_two()" << std::endl;
return 2 * x;
}
};
// Mockup non-member functions.
void action()
{
std::cout << "action()" << std::endl;
}
int times_two(int x)
{
std::cout << "times_two()" << std::endl;
return 2 * x;
}
/// @brief Guard that will unlock the GIL upon construction, and
/// reacquire it upon destruction.
struct no_gil
{
public:
no_gil() { state_ = PyEval_SaveThread();
std::cout << "no_gil()" << std::endl; }
~no_gil() { std::cout << "~no_gil()" << std::endl;
PyEval_RestoreThread(state_); }
private:
PyThreadState* state_;
};
/// @brief Guard that prints to std::cout.
struct echo_guard
{
echo_guard() { std::cout << "echo_guard()" << std::endl; }
~echo_guard() { std::cout << "~echo_guard()" << std::endl; }
};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose class and member-function.
python::class_<spam>("Spam")
.def("action", &spam::action)
.def("times_two", with<no_gil>(&spam::times_two))
;
// Expose non-member function.
python::def("action", &action);
python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
×_two));
}
及其用法:
>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
no_gil()
spam::times_two()
~no_gil()
10
>>> example.action()
action()
>>> example.times_two(21)
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42
请注意如何使用容器类型提供多个保护,例如boost::tuple
:
python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
×_two));
在 Python 中调用时,example.times_two(21)
会产生以下输出:
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42