[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