2

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

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

在这种情况下,有没有办法告诉 Boost::Python 尝试“向下转换”SmartPointer<Base>到 a 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>)
>>> 
4

1 回答 1

2

假设不能更改Derived::create_base()为 return SmartPointer<Derived>,这将允许在其他地方处理隐式转换,那么通过显式注册一个转换器来转换仍然是可能的SmartPointer<Derived>。当一个 Python 对象被传递给 C++ 时,Boost.Python 将在其注册表中查找任何可以构造必要的 C++ 对象的转换器。在这种情况下,需要间接,因为转换发生在HeldType暴露的 C++ 类的 s 上。的转换步骤DerivedHeldType

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

这是一个完整的示例,基于原始代码:

#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假设类型参数是HeldTypes。因此,boost::python::pointee<T>::type必须对HeldTypes 有效。
  • two_way_converter允许用户通过在的封闭命名空间中声明一个函数来启用他们自己的自定义策略来构造一个Sourcefrom 。第二个参数的值没有意义,因为它总是.TargetSource construct(Target, boost::type<Source>*)TargetNULL
  • 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 知道Base由 持有SmartPointer<Base>Derived派生自Base. 因此,Derived可能由 持有SmartPointer<Base>。当传递给 Python 时,Boost.Python 通过函数SmartPointer<Base>获取指向 C++ 对象的指针。get_pointer()静态类型Base用于在图中查找节点,然后进行遍历以尝试识别 C++ 对象的动态类型。这导致 Boost.Pythonexample.BaseSmartPointer<Base>指向动态类型为的对象时Base创建对象,并在指向动态类型为 的对象时创建对象。example.DerivedSmartPointer<Base>Derived

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

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

于 2013-09-11T15:36:44.800 回答