Boost :: Python中的智能指针转换

时间:2013-09-10 13:23:15

标签: c++ python boost smart-pointers boost-python

我尝试使用Boost :: Python为现有库创建Python绑定。 该库使用自定义智能指针(在以下示例中称为SmartPointer)。还有两个类,BaseDerived(继承自Base)。

当我想调用一个期望以SmartPointer<Derived>SmartPointer<Base>作为参数的函数时会出现问题。

在这种情况下,有没有办法告诉Boost :: Python尝试将SmartPointer<Base>“转发”到SmartPointer<Derived>?我知道这种“垂头丧气”可能会失败,但它会增加很多便利。

下面是一个最小的代码示例: (取决于您的系统,您可以使用g++ code.cpp -shared -o example.so -fPIC -I/usr/include/python3.2mu -lboost_python3 -lpython3.2mu

进行编译
#include <boost/python.hpp>
#include <iostream>

// ******** code to wrap ********
template <typename T>
class SmartPointer
{
public:
    explicit SmartPointer(T* p) : ptr(p) {}
    template <typename Y>
    explicit SmartPointer(Y* p) : ptr(static_cast<T*>(p)) {}
    template <typename Y>
    SmartPointer(SmartPointer<Y> const& src) : ptr(src.get()) {}
    T& operator*(void) const { return *ptr; }
    T* operator->(void) const { return ptr; }
    T* get(void) const { return ptr; }
protected:
    T* ptr;
};

class Base
{
public:
    virtual ~Base() {}
    virtual void say() const { std::cout << "Base" << std::endl; }
};

class Derived : public Base
{
public:
    virtual void say() const { std::cout << "Derived" << std::endl; }
    static SmartPointer<Base> create_base() { return SmartPointer<Base>(new Derived()); }
};

// ******** test functions ********
void test_basedirect(Base const& d) {
    d.say();
}

void test_basepointer(SmartPointer<Base> const& p) {
    p->say();
}

void test_deriveddirect(Derived const& d) {
    d.say();
}

void test_derivedpointer(SmartPointer<Derived> const& p) {
    p->say();
}

// ******** Boost::Python wrapping code ********
template <typename T>
T* get_pointer(SmartPointer<T> const& p) {
    return p.get();
}

namespace boost { namespace python {
    template <typename T>
    struct pointee<SmartPointer<T> > {
        typedef T type;
    };
}}

BOOST_PYTHON_MODULE(example) {
    using namespace boost::python;
    class_<Base, SmartPointer<Base>, boost::noncopyable>("Base", init<>())
        .def("say", &Base::say)
    ;
    class_<Derived, SmartPointer<Derived>, bases<Base>, boost::noncopyable>("Derived", init<>())
        .def("say", &Derived::say)
        .def("create_base", &Derived::create_base)
    ;
    def("test_basedirect", test_basedirect);
    def("test_basepointer", test_basepointer);
    def("test_deriveddirect", test_deriveddirect);
    def("test_derivedpointer", test_derivedpointer);
    implicitly_convertible<SmartPointer<Derived>, SmartPointer<Base> >();
}

和一个Python会话,显示对该函数的失败调用,期望SmartPointer<Derived>作为其参数:

>>> from example import *
>>> d = Derived.create_base()
>>> test_basedirect(d)
Derived 
>>> test_basepointer(d)
Derived 
>>> test_deriveddirect(d)
Derived 
>>> test_derivedpointer(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    example.test_derivedpointer(Derived)
did not match C++ signature:
    test_derivedpointer(SmartPointer<Derived>)
>>> 

1 个答案:

答案 0 :(得分:2)

假设有人无法更改Derived::create_base()以返回SmartPointer<Derived>,这将允许在其他地方处理隐式转换,那么通过为SmartPointer<Derived>显式注册转换器仍然可以进行转换。当Python对象被传递给C ++时,Boost.Python将在其注册表中查找可以构建必要C ++对象的任何转换器。在这种情况下,需要间接,因为转换发生在公开的C ++类的HeldType上。 Derived HeldType的转换步骤为:

  1. 如果Python对象包含DerivedSmartPointer<Base> C ++对象,则继续转换。
  2. 从Python对象中提取SmartPointer<Base>
  3. SmartPointer<Derived>调用构建SmartPointer<Base>的自定义函数。

  4. 以下是基于原始代码的完整示例:

    #include <iostream>
    #include <boost/python.hpp>
    #include <boost/static_assert.hpp>
    
    template <typename T>
    class SmartPointer
    {
    public:
      explicit SmartPointer(T* p) : ptr(p) {}
      template <typename Y>
      explicit SmartPointer(Y* p) : ptr(static_cast<T*>(p)) {}
      template <typename Y>
      SmartPointer(SmartPointer<Y> const& src) : ptr(src.get()) {}
      T& operator*(void) const { return *ptr; }
      T* operator->(void) const { return ptr; }
      T* get(void) const { return ptr; }
    protected:
      T* ptr;
    };
    
    class Base
    {
    public:
      virtual ~Base() {}
      virtual void say() const { std::cout << "Base" << std::endl; }
    };
    
    class Derived
      : public Base
    {
    public:
      virtual void say() const
      {
        std::cout << "Derived: " << this << std::endl;
      }
    
      static SmartPointer<Base> create_base()
      {
        return SmartPointer<Base>(new Derived());
      }
    };
    
    class OtherDerived
      : public Base
    {
    public:
      virtual void say() const
      {
        std::cout << "OtherDerived: " << this << std::endl;
      }
    
      static SmartPointer<Base> create_base()
      {
        return SmartPointer<Base>(new OtherDerived());
      }
    };
    
    void test_basedirect(Base const& d)                      { d.say();  }
    void test_basepointer(SmartPointer<Base> const& p)       { p->say(); }
    void test_deriveddirect(Derived const& d)                { d.say();  }
    void test_derivedpointer(SmartPointer<Derived> const& p) { p->say(); }
    
    // Boost.Python wrapping code.
    template <typename T>
    T* get_pointer(SmartPointer<T> const& p)
    {
      return p.get();
    }
    
    namespace boost {
    namespace python {
    
    template <typename T>
    struct pointee<SmartPointer<T> >
    {
      typedef T type;
    };
    
    } // namespace python
    } // namespace boost
    
    namespace detail {
    // @brief Construct Source from Target.
    template <typename Source,
              typename Target>
    Source construct_helper(Target& target)
    {
      // Lookup the construct function via ADL.  The second argument is
      // used to:
      // - Encode the type to allow for template's to deduce the desired
      //   return type without explicitly requiring all construct functions
      //   to be a template.
      // - Disambiguate ADL when a matching convert function is declared
      //   in both Source and Target's enclosing namespace.  It should
      //   prefer Target's enclosing namespace.
      return construct(target, static_cast<boost::type<Source>*>(NULL));
    }
    } // namespace detail
    
    /// @brief Enable implicit conversions between Source and Target types
    ///        within Boost.Python.
    ///
    ///        The conversion of Source to Target should be valid with
    ///        `Target t(s);` where `s` is of type `Source`.
    ///
    ///        The conversion of Target to Source will use a helper `construct`
    ///        function that is expected to be looked up via ADL.
    ///
    ///        `Source construct(Target&, boost::type<Source>*);`
    template <typename Source,
              typename Target>
    struct two_way_converter
    {
      two_way_converter()
      {
        // Enable implicit source to target conversion.
        boost::python::implicitly_convertible<Source, Target>();
    
        // Enable target to source conversion, that will use the convert
        // helper.
        boost::python::converter::registry::push_back(
          &two_way_converter::convertible,
          &two_way_converter::construct,
          boost::python::type_id<Source>()
        );
      }
    
      /// @brief Check if PyObject contains the Source pointee type.
      static void* convertible(PyObject* object)
      {
        // The object is convertible from Target to Source, if:
        // - object contains Target.
        // - object contains Source's pointee.  The pointee type must be
        //   used, as this is the converter for Source.  Extracting Source
        //   would cause Boost.Python to invoke this function, resulting
        //   infinite recursion.
        typedef typename boost::python::pointee<Source>::type pointee;
        return boost::python::extract<Target>(object).check() &&
               boost::python::extract<pointee>(object).check()
            ? object
            : NULL;
      }
    
      /// @brief Convert PyObject to Source type.
      static void construct(
        PyObject* object,
        boost::python::converter::rvalue_from_python_stage1_data* data)
      {
        namespace python = boost::python;
    
        // Obtain a handle to the memory block that the converter has allocated
        // for the C++ type.
        typedef python::converter::rvalue_from_python_storage<Source>
                                                                storage_type;
        void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
    
        // Extract the target.
        Target target = boost::python::extract<Target>(object);
    
        // Allocate the C++ type into the converter's memory block, and assign
        // its handle to the converter's convertible variable.  The C++ type
        // will be copy constructed from the return of construct function.
        data->convertible = new (storage) Source(
          detail::construct_helper<Source>(target));
      }
    };
    
    /// @brief Construct SmartPointer<Derived> from a SmartPointer<Base>.
    template <typename Derived>
    Derived construct(const SmartPointer<Base>& base, boost::type<Derived>*)
    {
      // Assumable, this would need to do more for a true smart pointer.
      // Otherwise, two unrelated sets of smart pointers are managing the
      // same instance.
      return Derived(base.get());
    }
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
    
      // Expose Base.
      python::class_<Base, SmartPointer<Base>, boost::noncopyable>(
          "Base", python::init<>())
        .def("say", &Base::say)
        ;
    
      // Expose Derived.
      python::class_<Derived, SmartPointer<Derived>, 
           python::bases<Base>, boost::noncopyable>(
             "Derived", python::init<>())
        .def("say", &Derived::say)
        .def("create_base", &Derived::create_base)
        .staticmethod("create_base");
        ;
    
      // Expose OtherDerived.
      python::class_<OtherDerived, SmartPointer<OtherDerived>, 
          python::bases<Base>, boost::noncopyable>(
            "OtherDerived", python::init<>())
        .def("say", &OtherDerived::say)
        .def("create_base", &OtherDerived::create_base)
        .staticmethod("create_base");
        ;
    
      // Expose Test functions.
      python::def("test_basedirect",          &test_basedirect);
      python::def("test_basepointer",         &test_basepointer);
      python::def("test_deriveddirect",       &test_deriveddirect);
      python::def("test_derivedpointer",      &test_derivedpointer);
    
      // Enable conversions between the types.
      two_way_converter<SmartPointer<Derived>,      SmartPointer<Base> >();
      two_way_converter<SmartPointer<OtherDerived>, SmartPointer<Base> >();
    }
    

    及其用法:

    >>> from example import *
    >>> d = Derived.create_base()
    >>> print d
    <example.Derived object at 0xb7f34b1c>
    >>> test_basedirect(d)
    Derived: 0x8f4de18
    >>> test_basepointer(d)
    Derived: 0x8f4de18
    >>> test_deriveddirect(d)
    Derived: 0x8f4de18
    >>> test_derivedpointer(d)
    Derived: 0x8f4de18
    >>> 
    >>> o = OtherDerived.create_base()
    >>> print o
    <example.OtherDerived object at 0xb7f34b54>
    >>> test_basedirect(o)
    OtherDerived: 0x8ef6dd0
    >>> test_basepointer(o)
    OtherDerived: 0x8ef6dd0
    >>> test_derivedpointer(o)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    Boost.Python.ArgumentError: Python argument types in
        example.test_derivedpointer(OtherDerived)
    did not match C++ signature:
        test_derivedpointer(SmartPointer<Derived>)
    

    关于实施的一些注释/评论:

    • two_way_converter假设类型参数为HeldType s。因此,boost::python::pointee<T>::type必须对HeldType s。
    • 有效
    • two_way_converter允许用户通过在Source的封闭命名空间中声明Target函数,启用自己的自定义策略,从Source construct(Target, boost::type<Source>*)构建Target。第二个参数的值没有意义,因为它始终是NULL
    • two_way_converter::convertible()的标准必须足够彻底,Source construct(Target)不会失败。

    此外,test_deriveddirect()有效,因为Boost.Python在从C ++对象创建Python对象时执行内省。暴露类时,Boost.Python构造一个包含类型信息的图形。当C ++对象传递给Python时,Boost.Python将横向移动图形,直到找到基于C ++对象的动态类型的相应Python类型。一旦找到,就会分配Python对象并保存C ++对象或其HeldType

    在示例代码中,Boost.Python知道BaseSmartPointer<Base>持有,而Derived来自Base。因此,Derived可以保留SmartPointer<Base>。当SmartPointer<Base>传递给Python时,Boost.Python通过get_pointer()函数获取指向C ++对象的指针。静态类型Base用于在图中查找节点,然后尝试遍历以尝试识别C ++对象的动态类型。这导致Boost.Python在example.Base指向动态类型为SmartPointer<Base>的对象时创建Base对象,并在example.Derived时创建SmartPointer<Base>对象指向动态类型为Derived的对象。

    >>> d = Derived.create_base()
    >>> print d
    <example.Derived object at 0xb7f34b1c>
    

    由于Boost.Python知道d包含C ++ Derived对象,因此对test_deriveddirect(d)的调用有效。