【问题标题】:Inheritance and shared_ptr ref parameters with Boost.PythonBoost.Python 的继承和 shared_ptr ref 参数
【发布时间】:2016-03-22 07:40:04
【问题描述】:

我遇到了 Boost.Python 的一个极端情况,它看起来应该可以工作,但没有。

我拥有的是一个 Base 和一个 Derived 类,我将它们存储在 python 端的 std::shared_ptr 中。我想做的是将 Derived 类型 shared_ptr 传递给通过引用接受 Base shared_ptr 的函数。

我进行了一些研究并了解了implicitly_convertible,并尝试使用它来解决问题,但没有成功(尽管它在其他一些情况下确实有帮助)。将 Derived 传递给接受 Base& 的函数可以使用此方法,但如果它们包含在 shared_ptr 中,则它会失败。

我目前得到的是以下消息:

Boost.Python.ArgumentError: Python argument types in
    test_bed_bindings.acceptBaseSharedPtrRef(Derived) did not match C++ signature:
    acceptBaseSharedPtrRef(std::shared_ptr<(anonymous namespace)::Base> {lvalue})

示例代码如下:

C++绑定代码

#define BOOST_PYTHON_STATIC_LIB
#define BOOST_PYTHON_USE_GCC_SYMBOL_VISIBILITY 1

#include <boost/optional.hpp>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include <iostream>
#include <memory>


namespace
{

  class Base
  {
  };

  class Derived : public Base
  {
  };

  std::shared_ptr<Base> getBaseSharedPtr()
  {
    auto retVal = std::make_shared<Base>();
    std::cout << "Creating Base shared_ptr - " << retVal.get() << std::endl;
    return retVal;
  }

  std::shared_ptr<Derived> getDerivedSharedPtr()
  {
    auto retVal = std::make_shared<Derived>();
    std::cout << "Creating Derived shared_ptr - " << retVal.get() << std::endl;
    return retVal;
  }

  void acceptBaseSharedPtrRef(std::shared_ptr<Base>& base)
  {
    std::cout << "acceptBaseSharedPtrRef() with " << base.get() << std::endl;
  }

  void acceptBaseSharedPtrConstRef(const std::shared_ptr<Base>& base)
  {
    std::cout << "acceptBaseSharedPtrConstRef() with " << base.get() << std::endl;
  }

  void acceptBaseSharedPtrCopy(std::shared_ptr<Base> base)
  {
    std::cout << "acceptBaseSharedPtrCopy() with " << base.get() << std::endl;
  }

  //

  void acceptBaseRef(Base base)
  {

  }

} // namespace

namespace bindings
{
  BOOST_PYTHON_MODULE(test_bed_bindings)
  {
    PyEval_InitThreads();
    Py_Initialize();

    using namespace boost::python;

    def("getBaseSharedPtr",            &::getBaseSharedPtr);
    def("getDerivedSharedPtr",         &::getDerivedSharedPtr);
    def("acceptBaseSharedPtrRef",      &::acceptBaseSharedPtrRef);
    def("acceptBaseSharedPtrConstRef", &::acceptBaseSharedPtrConstRef);
    def("acceptBaseSharedPtrCopy",     &::acceptBaseSharedPtrCopy);

    def("acceptBaseRef",     &::acceptBaseRef);

    class_<Base, std::shared_ptr<Base> >("Base")
        .def(init<>())
        ;

    class_<Derived, bases<Base>, std::shared_ptr<Derived> >("Derived")
        .def(init<>())
        ;

    implicitly_convertible<Derived, Base>();
    implicitly_convertible<std::shared_ptr<Derived>, std::shared_ptr<Base>>();

  } // BOOST_PYTHON

} // namespace bindings

Python 执行代码

import test_bed_bindings

baseObj = test_bed_bindings.Base()
derivedObj = test_bed_bindings.Derived()

test_bed_bindings.acceptBaseRef( baseObj )
test_bed_bindings.acceptBaseRef( derivedObj )

baseSharedPtr = test_bed_bindings.getBaseSharedPtr()
derivedSharedPtr = test_bed_bindings.getDerivedSharedPtr()

test_bed_bindings.acceptBaseSharedPtrCopy( baseSharedPtr )
test_bed_bindings.acceptBaseSharedPtrCopy( derivedSharedPtr )

test_bed_bindings.acceptBaseSharedPtrConstRef( baseSharedPtr )
test_bed_bindings.acceptBaseSharedPtrConstRef( derivedSharedPtr )

test_bed_bindings.acceptBaseSharedPtrRef( baseSharedPtr )
test_bed_bindings.acceptBaseSharedPtrRef( derivedSharedPtr )

样本输出

Creating Base shared_ptr - 0x276fdb8
Creating Derived shared_ptr - 0x276fde8
acceptBaseSharedPtrCopy() with 0x276fdb8
acceptBaseSharedPtrCopy() with 0x276fde8
acceptBaseSharedPtrConstRef() with 0x276fdb8
acceptBaseSharedPtrConstRef() with 0x276fde8
acceptBaseSharedPtrRef() with 0x276fdb8
Traceback (most recent call last):
  File "test_script.py", line 21, in <module>
    test_bed_bindings.acceptBaseSharedPtrRef( derivedSharedPtr )
Boost.Python.ArgumentError: Python argument types in
    test_bed_bindings.acceptBaseSharedPtrRef(Derived)
did not match C++ signature:
    acceptBaseSharedPtrRef(std::shared_ptr<(anonymous namespace)::Base> {lvalue})

【问题讨论】:

    标签: python c++ boost binding boost-python


    【解决方案1】:

    这是故意的。为了减少悬空引用的可能性并在语言之间提供明确的方向性,Boost.Python 将通过 const 引用将 rvalue 转换产生的 temporary 对象传递给函数。 implicit_convertible&lt;Source, Target&gt; 函数注册一个 rvalue from-Python 转换。由于转换器的结果是 rvalue,因此只能通过值或常量引用来接受它。


    当一个类通过boost::python::class_&lt;T, HeldType, Bases&gt;注册并且HeldType包装T时:

    • 生成的 Python 类嵌入了 HeldType 的实例
    • T 实例的 to-Python 转换器注册到 Python 类的实例
    • 将 Python 类实例的 lvalue 从 Python 转换器注册到 T 的实例
    • 注册从HeldType实例到Python对象的Python转换器
    • 将 Python 类实例的 lvalue 从 Python 转换器注册到 HeldType 的实例
    • 对于Bases 中的每个基,将 Python 类实例的 lvalue 从 Python 转换器注册到基中 T 的实例(不是基的 HeldType
    • 对于Bases 中的每个多态基,注册一个从基所持有的T 实例到Python 类的to-Python 转换器

    使用以下设置:

    class base {};
    class derived: public base {};
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
    
      python::class_<base, std::shared_ptr<base>>("Base");
      python::class_<derived, python::bases<base>,
        std::shared_ptr<derived>>("Derived");
    
      python::implicitly_convertible<std::shared_ptr<derived>,
                                     std::shared_ptr<base>>();
    }
    

    以下 lvalue from-Python 转换是可能的,因为 Python 对象包含 C++ 对象的实例:

    • example.Basebasebase&amp;const base&amp;std::shared_ptr&lt;base&gt;std::shared_ptr&lt;base&gt;&amp;const std::shared_ptr&lt;base&gt;&amp;
    • example.Derivedbasebase&amp;const base&amp;derivedderived&amp;const derived&amp;std::shared_ptr&lt;derived&gt;std::shared_ptr&lt;derived&gt;&amp;const std::shared_ptr&lt;derived&gt;&amp;

    以下到 Python 的转换是可能的:

    • basestd::shared_ptr&lt;base&gt;example.Base
    • derivedstd::shared_ptr&lt;derivedexample.Derived

    如果base 是多态的,则可以进行以下到 Python 的转换:

    • 动态类型为derived* 和静态类型为base* 的对象到example.Derived
    • std::shared_ptr&lt;base&gt; 持有 derivedexample.Derived 的实例

    由于通过implicitly_convertible 显式注册,以下右值 转换是可能的:

    • example.Derivedstd::shared_ptr&lt;base&gt;const std::shared_ptr&lt;base&gt;&amp;

    lvaluervalue 转换之间的区别在于目标 C++ 对象是否已经存在并保存在 Python 对象中。例如,example.Derivedbase&amp;左值 转换是可能的,因为example.Derived 拥有derived 的一个实例,它是一个base。另一方面,从example.Derivedstd::shared_ptr&lt;base&gt;&amp;左值 转换是不可能的,因为example.Derived 拥有一个std::shared_ptr&lt;derived&gt; 的实例,它不继承自std::shared_ptr&lt;base&gt;。因此,构造了一个具有未指定生命周期的 std::shared_ptr&lt;base&gt;,并将其作为 rvalue 参数传递给公开的函数。


    这是一个完整的例子demonstrating这些转换:

    #include <boost/python.hpp>
    #include <memory> // std::shared_ptr
    
    class base {};
    class derived: public base {};
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<base, std::shared_ptr<base>>("Base");
      python::class_<derived, python::bases<base>, 
        std::shared_ptr<derived>>("Derived");
    
      python::implicitly_convertible<std::shared_ptr<derived>,
                                     std::shared_ptr<base>>();
    
      python::def("base_value", +[](base){});
      python::def("base_ref", +[](base&){});
      python::def("base_cref", +[](const base&){});
    
      python::def("shared_base_value", +[](std::shared_ptr<base>){});
      python::def("shared_base_ref", +[](std::shared_ptr<base>&){});
      python::def("shared_base_cref", +[](const std::shared_ptr<base>&){});
    
      python::def("derived_value", +[](derived){});
      python::def("derived_ref", +[](derived&){});
      python::def("derived_cref", +[](const derived&){});
    
      python::def("shared_derived_value", +[](std::shared_ptr<derived>){});
      python::def("shared_derived_ref", +[](std::shared_ptr<derived>&){});
      python::def("shared_derived_cref", +[](const std::shared_ptr<derived>&){});
    }
    

    互动使用:

    >>> base = example.Base()
    >>> example.base_value(base)
    >>> example.base_ref(base)
    >>> example.base_cref(base)
    >>> example.shared_base_value(base)
    >>> example.shared_base_ref(base)
    >>> example.shared_base_cref(base)
    >>> 
    >>> derived = example.Derived()
    >>> example.base_value(derived)
    >>> example.base_ref(derived)
    >>> example.base_cref(derived)
    >>> example.shared_base_value(derived)
    >>> try:
    ...     got_exception = False
    ...     example.shared_base_ref(derived)
    ... except TypeError:
    ...     got_exception = True
    ... finally:
    ...     assert(got_exception)
    ...
    >>> example.shared_base_cref(derived)
    >>> example.derived_value(derived)
    >>> example.derived_ref(derived)
    >>> example.derived_cref(derived)
    >>> example.shared_derived_value(derived)
    >>> example.shared_derived_ref(derived)
    >>> example.shared_derived_cref(derived)
    

    【讨论】:

    • 感谢您对内部工作原理的深入解释。我已经从研究中解决了大部分问题,但我不确定我是否遗漏了什么。我想从这里我将实施某种解决方法。
    猜你喜欢
    • 2014-08-27
    • 1970-01-01
    • 2023-03-11
    • 2023-04-03
    • 2021-08-10
    • 1970-01-01
    • 2012-04-28
    • 1970-01-01
    • 2012-05-13
    相关资源
    最近更新 更多