Boost.Python函数指针作为类构造函数参数

1 投票
1 回答
2469 浏览
提问于 2025-04-16 03:42

我有一个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 个回答

2

好的,这个问题一般来说比较难回答。你遇到的问题的根本原因是,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::functionstd::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

撰写回答