Boost.Python:从 python 中的 C++ 对象继承:无法添加到 C++ 基础 class 列表

Boost.Python: inheret from C++ object in python: Cannot add to C++ base class list

我正在尝试通过继承扩展 Python 中现有的 C++ 对象。 我可以成功地做到这一点并且 运行 虚拟方法在 Python 中被覆盖。但是,当我尝试将 python 对象添加到 C++ Base 对象类型的指针列表(python class 已覆盖的 Base 对象)时,出现类型错误: 'Attempting to append an invalid type'

我确定此错误是由于从派生*到基础*开始没有 'implicitly_convertible' 功能。在 C++ 中,这将定义为:implicitly_convertible<[Derived_from_base],Base>();。 可以在 python 中定义吗?

我怎样才能做到这一点?

这是重现此行为的示例代码。

C++

struct Base {
    virtual ~Base() {}
    virtual int f() = 0;
};
struct A {
    std::vector<Base*>& GetBaseList() { return m_base_List; }
    std::vector<Base*> m_base_List;
};
struct BaseWrap : Base, wrapper<Base> {
    int f() { return this->get_override("f")(); }
};

BOOST_PYTHON_MODULE(sandbox)
{
    class_<BaseWrap, Base*, boost::noncopyable>("Base", no_init)
        .def("f", pure_virtual(&Base::f));

    class_<A, A*>("A", init<>())
        .add_property("baseList", make_function(&A::GetBaseList, return_internal_reference<>()));

    //implicitly_convertible<[Derived_from_base]*,Base*>();
    class_<std::vector<Base*>>("BaseList").def(vector_indexing_suite<std::vector<Base*>>());
}

Python 从沙箱导入 *

class derived(Base):
    def __init__(self):
        self.name = "test"
    def f(self):
        print("Hello Derived!")

d = derived()
d.f()          # Output: Hello Derived!

a = A()
a.baseList.append(d) # TypeError: Attempting to append an invalid type

任何帮助或想法将不胜感激。

BaseList.append()函数接收一个类型正确的参数;但是,该参数有一个不合适的 value。在 Python 中,derived 初始化程序未初始化其层次结构的 sandbox.Base 部分。这导致 Boost.Python 对象不包含 C++ BaseWrap 对象。因此,当 BaseList.append() 尝试提取 C++ BaseWrap 对象时,它会失败并抛出错误。

class derived(Base):
    def __init__(self):
        self.name = "test"
        # Base is not initialized.
    def f(self):
        print("Hello Derived!")

d = derived()
d.f() # `derived.f()` is resolved through Python's method-resolution-order.  
      # It is not invoking `BaseWrap::f()`.

a = A()
a.baseList.append(d) # d does not contain a BaseWrap object, so this throws.

要解决此问题,请在 derived.__init__():

中显式调用 Base.__init__()
class derived(Base):
    def __init__(self):
        self.name = "test"
        Base.__init__(self)

但是,尝试这样做会导致 BaseWrap 暴露的其他问题:

  • sandbox.Base class 必须可以从 Python 构造,因此绑定不能提供 boost::python::no_init 作为其初始化规范。通常,当 C++ 对象从 C++ 显式实例化并传递给 Python 时,人们只想使用 boost::python::no_init,例如通过工厂函数。
  • TBaseWrap时,Base*HeldType不满足HeldType的要求。特别是,HeldType 需要是:BaseWrap、派生自 BaseWrap 的 class 或 boost::python::pointee<Base*>::type 为 [=20= 的可解引用类型] 或从 BaseWrap 派生的 class。有关要求的详细信息,请参阅 class_ 规范。

这些可以通过公开 class 来解决,如下所示:

namespace python = boost::python;
python::class_<BaseWrap, boost::noncopyable>("Base", python::init<>())
  .def("f", python::pure_virtual(&Base::f))
  ;

这是一个完整的示例 demonstrating 将从公开的 C++ 派生的对象 class 传递给通过 vector_indexing_suite 公开的 C++ 向量:

#include <vector>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

struct base
{
  virtual ~base() {}
  virtual int perform() = 0;
};

struct base_wrap: base, boost::python::wrapper<base>
{
  int perform() { return int(this->get_override("perform")()) - 10; }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<base_wrap, boost::noncopyable>("Base", python::init<>())
    .def("perform", python::pure_virtual(&base::perform))
    ;

  python::class_<std::vector<base*>>("BaseList")
    .def(python::vector_indexing_suite<std::vector<base*>>())
    ;

  python::def("do_perform", +[](base* object) {
    return object->perform();
  });
}

交互使用:

>>> import example
>>> class derived(example.Base):
...     def __init__(self):
...         self.name = "test"
...         example.Base.__init__(self)
...     def perform(self):
...         return 42
...       
>>> d = derived()
>>> base_list = example.BaseList()
>>> base_list.append(d)
>>> assert(len(base_list) == 1)
>>> assert(base_list[0].perform() == 42)
>>> assert(example.do_perform(base_list[0]) == 32)

对于集合和指针,通常会有一些注意事项。在这种情况下:

  • BaseList 对象没有其元素所引用对象的共享所有权。请注意确保容器引用的对象的生命周期至少与容器本身一样长。在上面的示例中,如果对象 d 被删除,那么调用 base_list[0].perform() 会导致未定义的行为。
  • 无法迭代 base_list,因为迭代器的值将尝试执行 base* 到 Python 的转换,而该转换不存在。

上面的例子也说明了函数调度的不同。如果 Python 可以直接调用方法,它将使用自己的方法解析机制来实现。请注意 base_list[0].perform()example.do_perform(base_list[0]) return 是如何不同的值,因为一个是通过 base_wrap::perform() 发送的,它会操纵结果,而另一个则不会。

原代码中:

class derived(sandbox.Base):
    ...
    def f(self):
        print("Hello Derived!")

d = derived()
d.f()

因为 Python 知道 derived.f(),调用 d.f() 不会通过 BaseWrap::f() 调度。如果 BaseWrap::f() 被调用,它将被抛出,因为 derived.f() returned None,将无法转换为 int:

struct BaseWrap : Base, wrapper<Base> {
    int f() { return this->get_override("f")(); }
                        // ^~~ returns a boost::python::object, faling to 
                        //     extract `int` will throw. 
};