我尝试使用Boost :: Python为现有库创建Python绑定。
该库使用自定义智能指针(在以下示例中称为SmartPointer
)。还有两个类,Base
和Derived
(继承自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>)
>>>
答案 0 :(得分:2)
假设有人无法更改Derived::create_base()
以返回SmartPointer<Derived>
,这将允许在其他地方处理隐式转换,那么通过为SmartPointer<Derived>
显式注册转换器仍然可以进行转换。当Python对象被传递给C ++时,Boost.Python将在其注册表中查找可以构建必要C ++对象的任何转换器。在这种情况下,需要间接,因为转换发生在公开的C ++类的HeldType
上。 Derived
HeldType
的转换步骤为:
Derived
和SmartPointer<Base>
C ++对象,则继续转换。SmartPointer<Base>
。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
假设类型参数为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知道Base
由SmartPointer<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)
的调用有效。