我尝试使用Boost::P ython为现有库创建Python绑定。该库使用自定义智能指针(在以下示例中称为 SmartPointer
)。还有两个类,Base
和 Derived
(继承自 Base
)。
当我想调用一个期望以SmartPointer<Base>
作为参数的SmartPointer<Derived>
的函数时,就会出现问题。
在这种情况下,有没有办法告诉 Boost::P ython 尝试将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>)
>>>
假设不能更改Derived::create_base()
以返回SmartPointer<Derived>
,这将允许在其他地方处理隐式转换,那么通过显式注册SmartPointer<Derived>
转换器仍然可以进行转换。 当一个Python对象被传递给C++时,Boost.Python将在其注册表中查找任何可以构造必要的C++对象的转换器。 在这种情况下,需要间接寻址,因为转换发生在公开C++类的HeldType
上。 Derived
HeldType
的转换步骤如下:
- 如果 Python 对象同时包含
Derived
和SmartPointer<Base>
C++对象,请继续转换。 - 从 Python 对象中提取
SmartPointer<Base>
。 - 调用从
SmartPointer<Base>
构造SmartPointer<Derived>
的自定义函数。
下面是一个基于原始代码的完整示例:
#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
有效。 -
two_way_converter
允许用户启用自己的自定义策略,通过在Target
的封闭命名空间中声明一个Source construct(Target, boost::type<Source>*)
函数,从Target
构造一个Source
。 第二个参数的值没有意义,因为它将始终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 在SmartPointer<Base>
指向动态类型为 Base
的对象时创建一个example.Base
对象,并在SmartPointer<Base>
指向动态类型为 Derived
的对象时创建一个example.Derived
对象。
>>> d = Derived.create_base()
>>> print d
<example.Derived object at 0xb7f34b1c>
正如Boost.Python所知d
包含一个C++ Derived
对象,因此调用test_deriveddirect(d)
是有效的。