在 boost python 中暴露 C++ 接口
Posted
技术标签:
【中文标题】在 boost python 中暴露 C++ 接口【英文标题】:Exposing C++ interface in boost python 【发布时间】:2013-11-18 18:15:04 【问题描述】:示例代码说明:
struct Base
virtual int foo() = 0;
;
struct Derived : public Base
virtual int foo()
return 42;
;
Base* get_base()
return new Derived;
BOOST_PYTHON_MODULE(libTestMod)
py::class_<Base>("Base", py::no_init)
.def("foo", py::pure_virtual(&Base::foo));
py::def("get_base", get_base, py::return_internal_reference<>()); //ignore mem leak
Base::foo 在 python 中不会被覆盖
Base:foo 将在 c++ 中实现,但不应暴露给 python
尝试了上面的代码,但编译失败。
更新: 编译错误:
/path/to/boostlib/boost/1.53.0-0/common/include/boost/python/object/value_holder.hpp:66:11: error: cannot declare field 'boost_1_53_0::python::objects::value_holder<Base>::m_held' to be of abstract type 'Base'
Main.C:59:8: note: because the following virtual functions are pure within 'Base':
Main.C:61:15: note: virtual int Base::foo()
【问题讨论】:
您介意显示编译错误,或者您的问题是什么? 包含编译错误。 显然py::class_<Base>("Base", py::no_init)
想要创建Base
的实例,这是不可能的,因为Base
是抽象的。不过我对 boost python 没有经验,无法详细说明问题所在。
【参考方案1】:
抽象 C++ 类不能以这种方式暴露给 Boost.Python。 Boost.Python tutorial 给出了如何公开纯虚函数的示例。简而言之,当使用boost::python::pure_virtual
装饰方法时,需要创建一个包装器类型以允许C++对虚函数进行多态解析,而虚函数实现将在Python对象的层次结构中委托多态解析函数。
struct BaseWrap : Base, boost::python::wrapper<Base>
int foo()
return this->get_override("foo")();
;
...
boost::python::class_<BaseWrap>("Base", ...)
.def("foo", boost::python::pure_virtual(&Base::foo))
;
详细来说,当一个类型通过boost::python::class_
公开时,HeldType
默认为被公开的类型,HeldType
是在Python对象中构造的。 class_
文档指出:
模板参数:
T
: 被包装的类HeldType
:指定实际嵌入在包装T
实例 [...] 的 Python 对象中的类型。默认为T
。
因此,boost::python::class_<Base>
将失败,因为T = Base
和HeldType = Base
,Boost.Python 将尝试将HeldType
的对象实例化为代表Base
实例的Python 对象。此实例化将失败,因为Base
是一个抽象类。
这是一个完整的例子,展示了BaseWrap
类的使用。
#include <boost/python.hpp>
struct Base
virtual int foo() = 0;
virtual ~Base()
;
struct Derived : public Base
virtual int foo()
return 42;
;
Base* get_base()
return new Derived;
namespace python = boost::python;
/// @brief Wrapper that will provide a non-abstract type for Base.
struct BaseWrap : Base, python::wrapper<Base>
BaseWrap()
BaseWrap(const Base& rhs)
: Base(rhs)
int foo()
return this->get_override("foo")();
;
BOOST_PYTHON_MODULE(example)
python::class_<BaseWrap>("Base")
.def("foo", python::pure_virtual(&Base::foo));
;
python::def("get_base", &get_base,
python::return_value_policy<python::manage_new_object>());
及其用法:
>>> import example
>>> class Spam(example.Base):
... pass
...
>>> Spam().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: Pure virtual function called
>>> class Egg(example.Base):
... def foo(self):
... return 100
...
>>> e = Egg()
>>> e.foo()
100
>>> d = example.get_base()
>>> d.foo()
42
可以在 Boost.Python 中公开一个抽象类,方法是在没有默认初始化程序 (boost::python::no_init
) 和不可复制 (boost::noncopyable
) 的情况下公开它。缺少初始化程序会阻止 Python 类型从它派生,从而有效地防止覆盖。此外,Base::foo()
由Derived
在 C++ 中实现的实现细节无关紧要。如果 Python 根本不应该知道 foo()
方法,则省略通过 def()
公开它。
#include <boost/python.hpp>
struct Base
virtual int foo() = 0;
virtual ~Base()
;
struct Derived
: public Base
virtual int foo()
return 42;
;
struct OtherDerived
: public Base
virtual int foo()
return 24;
;
Base* get_base()
return new Derived;
Base* get_other_base()
return new OtherDerived;
BOOST_PYTHON_MODULE(example)
namespace python = boost::python;
python::class_<Base, boost::noncopyable>("Base", python::no_init)
;
python::class_<Derived, python::bases<Base> >("Derived", python::no_init)
.def("foo", &Base::foo)
;
python::class_<OtherDerived, python::bases<Base> >(
"OtherDerived", python::no_init)
;
python::def("get_base", &get_base,
python::return_value_policy<python::manage_new_object>());
python::def("get_other_base", &get_other_base,
python::return_value_policy<python::manage_new_object>());
互动使用:
>>> import example
>>> b = example.get_base()
>>> b.foo()
42
>>> b = example.get_other_base()
>>> b.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'OtherDerived' object has no attribute 'foo'
【讨论】:
上述解决方案只有在Base
的所有纯虚函数都暴露在python中时才有效。有没有办法只公开Base
的一部分功能?
@balki:上面暴露的类型没有纯虚函数,暴露类型既不需要也不暴露成员函数。如果foo()
不应该暴露,那么不要通过class_
上的def()
暴露它。如果从get_base()
返回的类型需要比BaseWrap
提供的接口更窄的接口,则将Derived
公开为仅具有所需方法和默认Bases
模板参数的类型。
我看到我们可以通过不指定def
来避免在python中暴露。但是它需要BaseWrap
来实现所有的纯虚函数。即用 dummy impl 重写 Base
的接口。我想没有更好的办法了。【参考方案2】:
抽象类实际上可以在没有包装器的情况下暴露给 Boost.Python。 诀窍是使用 boost::noncopyable 定义您的类并避免 pure_virtual 方法包装器。
这是更正后的代码(使用 Boost.Python 1.47.0 和 Python 2.7.6 测试):
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>
struct Base
virtual int foo() = 0;
;
struct Derived : public Base
virtual int foo()
return 42;
;
Base* get_base()
return new Derived;
BOOST_PYTHON_MODULE(libTestMod)
namespace py = boost::python;
py::class_<Base, boost::noncopyable>("Base", py::no_init)
.def("foo", &Base::foo);
py::def("get_base", get_base,
py::return_value_policy<py::reference_existing_object>()); //ignore mem leak
测试:
$ python
Python 2.7.6 (default, Mar 31 2014, 16:04:58)
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import libTestMod
>>> base = libTestMod.get_base()
>>> print base.foo()
42
【讨论】:
如何导出派生类?如果我这样做:py::class_<Derived, boost::noncopyable>("Derived", py::init<>()).def("foo", &Derived::foo);
我得到Traceback (most recent call last): File "code.py", line 3, in <module> print base.foo() Boost.Python.ArgumentError: Python argument types in Derived.foo(Derived) did not match C++ signature: foo(Derived lvalue)
导出派生类为:py::class_<Derived, py::bases<Base> >("Derived");
foo() 方法将被自动继承。以上是关于在 boost python 中暴露 C++ 接口的主要内容,如果未能解决你的问题,请参考以下文章
将 C++ 暴露给来自 BoostPython 的 Python 错误