假设不能更改Derived::create_base()
为 return SmartPointer<Derived>
,这将允许在其他地方处理隐式转换,那么通过显式注册一个转换器来转换仍然是可能的SmartPointer<Derived>
。当一个 Python 对象被传递给 C++ 时,Boost.Python 将在其注册表中查找任何可以构造必要的 C++ 对象的转换器。在这种情况下,需要间接,因为转换发生在HeldType
暴露的 C++ 类的 s 上。的转换步骤Derived
是HeldType
:
Derived
如果 Python 对象同时包含SmartPointer<Base>
C++ 对象,则继续转换。
SmartPointer<Base>
从 Python 对象中提取。
- 调用从 构造的自定义
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
from 。第二个参数的值没有意义,因为它总是.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>
。当传递给 Python 时,Boost.Python 通过函数SmartPointer<Base>
获取指向 C++ 对象的指针。get_pointer()
静态类型Base
用于在图中查找节点,然后进行遍历以尝试识别 C++ 对象的动态类型。这导致 Boost.Pythonexample.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)
有效。