Boost.Python函数指针作为类构造函数参数
我有一个C++类,它的构造函数需要一个函数指针(float(*myfunction)(vector<float>*)
)。
我已经把一些函数指针暴露给Python了。
使用这个类的理想方式大概是这样的:
import mymodule
mymodule.some_class(mymodule.some_function)
所以我这样告诉Boost关于这个类的信息:
class_<SomeClass>("some_class", init<float(*)(vector<float>*)>);
但是当我尝试编译的时候,我遇到了:
error: no matching function for call to 'register_shared_ptr1(Sample (*)(std::vector<double, std::allocator<double> >*))'
所以,有没有人能给我一些建议,告诉我怎么解决这个错误,同时又不失去函数指针带来的灵活性(也就是说,不想退回到用字符串来表示要调用哪个函数)?
另外,我写这段代码用C++的主要目的是为了速度。所以如果我还能保持这个优势就好了(函数指针在初始化时被赋值给一个成员变量,之后会被调用超过一百万次)。
1 个回答
好的,这个问题一般来说比较难回答。你遇到的问题的根本原因是,Python中并没有一种类型完全等同于C语言的函数指针。Python的函数有点像,但它们的接口不太匹配,原因有几个。
首先,我想提一下这里有一种技术,可以用来包装构造函数:http://wiki.python.org/moin/boost.python/HowTo#namedconstructors.2BAC8factories.28asPythoninitializers.29。这个技术让你可以为你的对象写一个__init__
函数,而这个函数并不直接对应于一个实际的C++构造函数。还要注意,如果你的对象不能使用默认构造函数,你可能需要在boost::python::class_
的构造中指定boost::python::no_init
,然后再定义一个真正的__init__
函数。
回到问题上:你通常想传入的函数是不是只有一小部分?如果是这样,你可以声明一个特殊的枚举(或者专门的类),然后重载你的构造函数,让它接受这个枚举,用它来查找真实的函数指针。虽然你不能直接从Python调用这些函数,但这样做也还不错,性能和使用真实函数指针是一样的。
如果你想提供一个通用的方法,适用于任何可调用的Python对象,那事情就复杂了。你需要为你的C++对象添加一个构造函数,接受一个通用的函数对象,比如使用boost::function
或std::tr1::function
。如果你愿意,可以替换掉现有的构造函数,因为函数指针会正确转换为这种类型。
所以,假设你已经为SomeClass
添加了一个boost::function
构造函数,你应该在你的Python包装代码中添加这些函数:
struct WrapPythonCallable
{
typedef float * result_type;
explicit WrapPythonCallable(const boost::python::object & wrapped)
: wrapped_(wrapped)
{ }
float * operator()(vector<float>* arg) const
{
//Do whatever you need to do to convert into a
//boost::python::object here
boost::python::object arg_as_python_object = /* ... */;
//Call out to python with the object - note that wrapped_
//is callable using an operator() overload, and returns
//a boost::python::object.
//Also, the call can throw boost::python::error_already_set -
//you might want to handle that here.
boost::python::object result_object = wrapped_(arg_as_python_object);
//Do whatever you need to do to extract a float * from result_object,
//maybe using boost::python::extract
float * result = /* ... */;
return result;
}
boost::python::object wrapped_;
};
//This function is the "constructor wrapper" that you'll add to SomeClass.
//Change the return type to match the holder type for SomeClass, like if it's
//held using a shared_ptr.
std::auto_ptr<SomeClass> CreateSomeClassFromPython(
const boost::python::object & callable)
{
return std::auto_ptr<SomeClass>(
new SomeClass(WrapPythonCallable(callable)));
}
//Later, when telling Boost.Python about SomeClass:
class_<SomeClass>("some_class", no_init)
.def("__init__", make_constructor(&CreateSomeClassFromPython));
我没有详细说明如何在Python和C++之间转换指针——这显然是你需要解决的问题,因为涉及到对象的生命周期。
如果你需要从Python调用你传入这个函数的函数指针,那么你需要在某个时候使用Boost.Python定义这些函数。这个第二种方法在定义的函数上工作得很好,但调用它们会比较慢,因为每次调用时对象都会不必要地在Python和C++之间转换。
为了解决这个问题,你可以修改CreateSomeClassFromPython
,让它识别已知或常见的函数对象,并用它们的真实函数指针替换掉。你可以在C++中使用object1.ptr() == object2.ptr()
来比较Python对象的身份,这相当于Python中的id(object1) == id(object2)
。
最后,你当然可以将通用方法和枚举方法结合起来。但在这样做时要注意,boost::python的重载规则与C++不同,这在处理像CreateSomeClassFromPython
这样的函数时可能会出问题。Boost.Python会按照定义的顺序测试函数,看看运行时的参数是否可以转换为C++的参数类型。因此,CreateSomeClassFromPython
会阻止在它之后定义的单参数构造函数被使用,因为它的参数可以匹配任何Python对象。确保把它放在其他单参数__init__
函数之后。
如果你发现自己经常需要做这种事情,可能想看看通用的boost::function包装技术(在同一页面上提到的命名构造函数技术):http://wiki.python.org/moin/boost.python/HowTo?action=AttachFile&do=view&target=py_boost_function.hpp。