[C++-sig] set python attribute from C++

David Abrahams dave at boostpro.com
Thu Jul 17 17:54:07 CEST 2008


on Thu Jul 17 2008, Gennadiy Rozental <rogeeff-AT-gmail.com> wrote:

> David Abrahams <dave <at> boostpro.com> writes:
>> > This one does work. But if you switch to intrusive_ptr it;s not 
>> > anymore ;(. And this is exactly the scenario I am using. Why 
>> > is that??
>> 
>> shared_ptr is special.  Custom deleters allow us to do some magical
>> things, by maintaining ownership of the Python object that wraps 
>> the C++ object.  
>
> Why is that by the way? It seems to me that intrusive_ptr is treated like
> distant relative to shared_ptr, while the only real difference is in storage
> policy for the counter.

It doesn't have a deleter.

>
>> Why do you want to use intrusive_ptr here?
>
> It's not even intrusive_ptr, but rather our own home grown smart
> pointer with properties similar to intrusive_ptr. This is forced on me
> by the framework I am working in.

I'm not entirely convinced that you need your own home grown smart
pointer in the particular code we're discussing.  Any wrapped T is
convertible to shared_ptr<T>, no matter what method is used to "hold"
the T in the class_<T, ...> declaration.

>> > What's more my problem is a bit more complicated. What I ma after is to
>> > initialize object A in it's init method (ideally would be in 
>> > constructor, but
>> > this is obviously impossible - derived class constructor in 
>> > python is not even called yet). 
>> 
>> I don't understand what you're saying.  All C++ class instances will be
>> constructed with a real call to their constructor.
>
> I mean I can't do what I am doing inside init function inside A
> constructor, because constructor of derived class in not called yet.

Hm.

>> > So the usage scenario is something like this:
>> >
>> > // this may create objects based on pure C++ classes 
>> > // or classes implemented in python.
>> > boost::intrusive_ptr<Base> obj = Factory::create(....); 
>> 
>> This can't work for "classes implemented in Python."  The intrusive_ptr
>> will not be able to maintain Python's reference count.
>
> This is really out of my hands. All our objects are memory managed
> through these intrusive_ptr like smart pointer. I guess what I need is
> to prevent Boost.Python from managing the C++ objects and manually
> sync lifetime of C++ anf python object.
>  
>> It sounds like you're trying to invert the natural ownership
>> relationship.  Python wrapper objects own their underlying C++
>> (sub)objects, not the other way around.  
>
> Ok. Here is my latest attempt schematically:
>
> class Base {
>   // this one is intrusively reference counted
>   // any framework object is required to inherit from it
>
>   ....
> };
>
> class_<Base,intrusive_ptr<Base>,noncopyable)( "Base" )
              ^^^^^^^^^^^^^^^^^^^

This automatically ensures that you can convert Python objects derived
from the Python Base into an intrusive_ptr<Base>.  Converting in the
other direction works too, but it produces a new Python object.

> ....
> ;
>
> class PythonBase : public Base {
> public:
>      ....
>      void init( bp::object const& o ) { m_object = o; }
>
>      void set_field( std::string const& field, intrusive_ptr<Base> value )
>      {
>           // here I am using global conversion registry I maintain
>           m_object.attr( field ) = convert_to_python_object( value )

I don't fully understand, but does it matter?

>      }
>
> private:
>      bp::object m_object;
> };
>
> class_<PythonBase,PythonBase*,noncopyable>( "PythonBase" )
                    ^^^^^^^^^^^
Very dangerous; could lead to leaks of C++ objects.  However if you want
the C++ object to own a Python object, it seems like an option.

> ....
> ;
>
> Base*
> PythonFactory::create(...) 
> {
>    bp::object obj = m_py_type();

If m_py_type is the PythonBase Python class, there is nothing managing
the C++ PythonBase object's lifetime.  The fact that PythonBase is
derived from Base and base is held by an intrusive_ptr makes no
difference.  OTOH, If m_py_type is the "Base" python class, the
following extract should fail.

>    PythonBase* ptr = bp::extract<PythonBase*>( obj );
>
>    ptr->init( obj );

Now obj /and/ the unmanaged C++ PythonBase object are both managing
the lifetime of the python object

>    return ptr;

So I guess if you capture the Base* with an intrusive_ptr here, you're
OK (so far).  However, you haven'd dealt with the possibility of an
exception during create().

> }

> On python side I've can now derive from PythonBase:
>
> class Derived(PythonBase):
>     pass
>
> Now i am seems to be able to create instances of Derived class and set their
> field from c++. Does this solution make sence?

I'm not 100% certain, but it looks like it will leak C++ PythonBase
instances.  You might try putting some tracing statements in the ctors
and dtors to see if they are actually going away when they should.

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com



More information about the Cplusplus-sig mailing list