[C++-sig] Best way to register shared_ptr<const T>
Alex Mohr
amohr at pixar.com
Fri Jan 25 21:16:17 CET 2008
> Can you post some complete example?
No, I can't. It's way too much code and very specific to our smart
pointers. But I can give you a sense of what it could look like for
what I imagine is a common case.
Alex
Suppose you have:
// manages pointee's lifetime using an intrusive reference count,
// like intrusive_ptr. T must derive from ref_base, which contains
// the reference count.
template <class T> class ref_ptr;
// a smart pointer that can detect when its pointee has been destroyed,
// but does not influence the lifetime of the pointee. also,
// weak_ptr<void> can detect when its pointee (of any type) has been
// destroyed. assume that ref_ptr<T> may be implicitly converted to
// weak_ptr<T> and weak_ptr<void>.
template <class T> class weak_ptr;
// some object which may be pointed to by ref_ptr and weak_ptr.
class foo : public ref_base {};
// wrap foo, and enable identity tracking.
bp::class_<foo, ref_ptr<foo> >("foo")
.def(track_identity())
;
// The key here is to implement some sort of mapping between python
// objects and c++ objects. The code below assumes an api like this:
void
set_python_identity(weak_ptr<void> const &cpp, PyObject *python);
PyObject *
get_python_identity(weak_ptr<void> const &cpp);
// I'll leave the issue of lifetime management wrt this mapping up to
// you. I think in this case, the right thing might be to keep python
// weak references for the python objects. Another challenge is how to
// clean expired objects from the mapping. Of course, if you're willing
// to store the python object weak reference inside the C++ object
// itself (say, alongside the ref count), then this is much easier.
// This code is similar to the code we have in production use, though
// I may have forgotten some things, have typos, and certainly I haven't
// tried compiling it.
template <class Ptr>
struct ref_ptr_from_python {
typedef typename get_pointee<Ptr>::type pointee;
ref_ptr_from_python() {
bp::converter::registry::insert(&convertible, &construct,
bp::type_id<Ptr>());
}
private:
static void *convertible(PyObject *p) {
if (p == Py_None)
return p;
return bp::converter::get_lvalue_from_python
(p, bp::converter::registered<pointee>::converters);
}
static void construct(PyObject *source, bp::converter::
rvalue_from_python_stage1_data *data) {
void *const storage = ((bp::converter::
rvalue_from_python_storage<Ptr>*)data)->storage.bytes;
// None case
if (data->convertible == source)
new (storage) Ptr();
else {
Ptr ptr(static_cast<pointee*>(data->convertible));
new (storage) Ptr(ptr);
// set python identity
set_python_identity(ptr, source);
}
data->convertible = storage;
}
};
template <class Ptr>
struct ref_ptr_to_python_replacement {
// store the original to-python converter. we do this in a static
// member since there is only one converter for a Ptr type.
static bp::converter::to_python_function_t original_converter;
// this signature has to match to_python_function_t.
static PyObject *convert(void const *x) {
// see boost/python/converter/as_to_python_function.hpp
Ptr const &ptr = *const_cast<Ptr*>(static_cast<Ptr const*>(x));
// try to get an existing python identity.
if (PyObject *id = get_python_identity(ptr))
return id;
// otherwise, use the original converter, and set that as the
// identity.
PyObject *ret = original_converter(x);
set_python_identity(ptr, ret);
return ret;
}
};
template <class Ptr>
bp::converter::to_python_function_t
ret_ptr_to_python_replacement<Ptr>::original_converter = 0;
template <class Ptr>
struct ref_ptr_to_python {
ref_ptr_to_python() {
bp::to_python_converter<Ptr, ref_ptr_to_python<Ptr> >();
}
static PyObject *convert(Ptr const &p) {
typedef typename get_pointee<Ptr>::type pointee;
// null -> None
if (not p)
return bp::detail::none();
if (PyObject *id = get_python_identity(p))
return id;
// otherwise make a new python object, and set identity.
PyObject *ret = bp::objects::make_ptr_instance
<pointee, bp::objects::
pointer_holder<Ptr, pointee> >::execute(p);
set_python_identity(p, ret);
return ret;
}
};
// the identity tracking mechanism.
struct track_identity : bp::def_visitor<track_identity> {
friend class bp::def_visitor_access;
template <class WrapperPtr, class Wrapper, class T>
static void register_conversions(Wrapper *, T *) {
// need to rebind the smart pointer template with T,
// in case T is different from Wrapper (python virtual dispatch)
// I'll just assume ref_ptr<T> here.
typedef ref_ptr<T> ptr_type;
// register from-python conversions
ref_ptr_from_python<ptr_type>();
// replace the to_python conversion that boost.python registered
// for this class. we do this to support object-identity
// tracking.
//
// note that we do this for the "wrapped type", since that's all
// that boost.python's registered a conversion for. the
// unwrapped type is handled separately -- we don't have to
// replace an existing converter, we just register our own.
bp::converter::registration *r =
const_cast<bp::converter::registration *>
(bp::converter::registry::query(bp::type_id<WrapperPtr>()));
assert(r);
ref_ptr_to_python_replacement<WrapperPtr>::original_converter =
r->m_to_python;
r->m_to_python =
ref_ptr_to_python_replacement<WrapperPtr>::convert;
if (not is_same<Wrapper, T>::value)
ref_ptr_to_python<ptr_type>();
}
template <class CLS>
void visit(CLS &c) const {
typedef typename CLS::wrapped_type type;
typedef typename CLS::metadata::held_type_arg ptr_type;
register_conversions<ptr_type>
((type *)0, bp::detail::unwrap_wrapper((type *)0));
}
};
More information about the Cplusplus-sig
mailing list