[C++-sig] Boost.Python to_python_indirect and intrusive_ptr.
Nicolas Colombe
nicolas.colombe at gmail.com
Sat Apr 3 20:29:43 CEST 2010
Hi everyone.
I'm trying to use Boost.Python to expose some of my C++ classes and I
encountered an odd behaviour of the to_python_indirect result converter for
intrusive_ptr with specific classes.
Here is the deal:
I'm using a component-based design for my classes, meaning an aggregation of
objects with different features. These objects are ref-counted, and if they
are in the same aggregation, they share the same lifetime.
That is to say, as long as one of the components in the "ring" has a
strictly positive reference counter, every component in the ring survive
even if their ref-count drops to zero.
I use boost::intrusive_ptr to manipulate them easily.
There is the basic interface:
class Component
{
void AddRef();
void Release();
unsigned int GetRefCount();
bool AddComponent(Component*);
bool RemoveComponent(Component*);
Component* ComponentIterator(Component*); //-->Used to parse the
ring.
virtual some_virtual_fn();
};
To avoid lifetime problems when manipulating an object both from C++ and
python side, i want python to manipulate components only through
boost::intrusive_ptr.
There is the exposition code:
//-->Object wrapper
struct Component_wrapper : SandBoxProj::Component, bp::wrapper<
SandBoxProj::Component > {
Component_wrapper( ) :Component( ), bp::wrapper< SandBoxProj::Component
>(){}
virtual some_virtual_fn() {
//overriding code...
}
default_some_virtual_fn() {
Component::some_virtual_fn();
}
};
//-->Some declarations that boost.python needs to manipulate
boost::intrusive_ptr
namespace boost { namespace python {
template <class T> struct pointee< intrusive_ptr<T> > //-->pointee
struct for intrusive_ptr holder
{
typedef T type;
};
struct make_owning_intrusive_ptr_holder //-->ResultConverter to make an
intrusive_ptr from a raw one, inspired from to_python_indirect.hpp:79
{
template <class T>
static PyObject* execute(T* p)
{
typedef objects::pointer_holder<boost::intrusive_ptr<T>, T> holder_t;
boost::intrusive_ptr<T> ptr(p);
return boost::python::objects::make_ptr_instance<T,
holder_t>::execute(ptr);
}
};
struct return_by_intrusive_ptr //-->corresponding
ResultConverterGenerator
{
template <class T>
struct apply
{
typedef to_python_indirect<T, make_owning_intrusive_ptr_holder> type;
//--> here is the usage of to_python_indirect
};
};
}}
bp::class_<
Component_wrapper,boost::intrusive_ptr<Component_wrapper>,boost::noncopyable
>( "Component", bp::init< >() )
.def( "AddComponent", &Component::AddComponent, ( bp::arg("arg0") ) )
.def( "ComponentIterator",&Component::ComponentIterator, (
bp::arg("arg0") ), bp::return_value_policy< bp::return_by_intrusive_ptr >()
) //-->this class uses the converter.
.def(
"some_virtual_fn",&Component::some_virtual_fn,&Component_wrapper::default_some_virtual_fn)
.def( "GetRefCount",&Component::GetRefCount)
.def( "RemoveComponent",&Component::RemoveComponent, ( bp::arg("arg0") )
) ;
And finally there is a scripts that uses this class and its output:
>>>from pythonbinder import *
>>>import gc
>>>Comp1=Component()
>>>Comp2=Component()
>>>print Comp2.GetRefCount()
1
//-->Ok, I manipulate an intrusive_ptr
>>>Comp2.AddComponent(Comp1)
>>>iter=None
>>>iter=Comp1.ComponentIterator(iter)
>>>print iter
<pythonbinder.Component object at 0x026DA630> //-->Ok, There is my
C++ object
>>>iter=Comp1.ComponentIterator(iter)
>>>print iter
<pythonbinder.Component object at 0x026DA600>
>>>print iter.GetRefCount()
1
//-->What the ? iter is supposed to be an intrusive ptr. After a look at
to_python_indirect, it seems that iter==Comp2, the python object wrapping
the intrusive_ptr was just addref'ed. Ok, fair enough.
>>>iter=Comp1.ComponentIterator(iter)
>>>print iter
None
//-->Now the troubles begin.
>>>del Comp2
>>>gc.collect() //-->ensure deletion, Comp2 is still alive from the C++
side, because Comp1 is alive.
>>>iter=Comp1.ComponentIterator(iter) //May crash
>>>print iter
//-->if we made it through, a weak_ptr to some random structure.
After that, i figured out that i never went through the ResultConverters i
declared. Let's look at the execute function from to_python_indirect,
template <class U>
inline PyObject* execute(U const& x, mpl::false_) const
{
U* const p = &const_cast<U&>(x);
if
(is_polymorphic<U>::value)
//-->My type is polymorphic
{
if (PyObject* o = detail::wrapper_base_::owner(p))
//-->Ok, that's why I have a refcount of 1, no matter how many python
intrusive_ptr wrappers I seem to have
return incref(o);
}
return
MakeHolder::execute(p); //-->There
is the call to my result converter.
}
What happen is that after deletion of the last python wrapping of the
intrusive_ptr, which is o in the code above, the Component_wrapper, which is
partly a python object, is not deleted (the component ring still holds it),
but it still reference its last owner which is the last instance_holder that
just got deleted, so the object returned is a random memory garbage.
Changing the holder type fromintrusive_ptr<Component_wrapper> to
intrusive_ptr<Component> solves the above problems but then it's not
possible to use the class Component_wrapper as a base in python anymore.
I tried to create a class derived from Component in python and the
constructor of Component_wrapper was not called. (purpose is overriding
virtual functions in python)
I wondered if I'm going the wrong way, or if I discovered a bug.
It may be a limitation of the library, which cannot understand that an
object survived the deletion of its last holder.
Maybe if i made my own version of to_python_indirect but removing the
is_polymorphic case, it would work?
Is there a fundamental reason to this test, other than saving memory?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cplusplus-sig/attachments/20100403/4324afe8/attachment.html>
More information about the Cplusplus-sig
mailing list