[C++-sig] using intrusive_ptr objects with boost python

Jeff Webb jeff.webb at nta-inc.net
Tue Jun 19 22:32:53 CEST 2007


Let me first say that I am extremely impressed with boost python's elegant and powerful design.  I would not have thought that it was possible to integrate C++ and python code in such a nice manner.  Thanks for all the hard work and thoughtful design.

I am writing a C++ library from scratch, and I want to develop boost python bindings for it simultaneously.  My C++ library will instantiate many small objects that hold references to other objects.  I had come up a consistent scheme for managing the objects using C++, but when I started trying to wrap the library using boost, I found that this scheme didn't mesh well with python's reference counting form of memory management.  After quite a bit of reading, experimentation, and contemplation, it appears to me that using reference-counting smart pointers in C++ would be the most consistent and seamless way to provide a rich python binding for this type of library.  Do others agree with this assertion?

I wrote a simple test library using shared_ptr objects and found that wrapping it was very natural.  Things 'just worked' as I would have hoped.  The source code for this library is attached to this email as 's_ptr_lib.cpp'.  A sample ipython session is shown below.

 In [1]: from s_ptr_lib import *
 In [2]: c1 = C('c1'); c2 = C('c2')
 C(c1)
 C(c2)
 In [3]: c1.set_ptr(c2)
 In [4]: ptr = c1.get_ptr()
 In [5]: ptr is c2
 Out[5]: True  
 In [6]: del(c2)
 In [7]: del(ptr)
 In [8]: del(c1)
 ~C(c1)
 ~C(c2)

Very nice!  Unfortunately, I think the overhead of using a shared_ptr will be too high, since my library will instantiate many small objects.  I then discovered the intrusive_ptr, and I think it would be a better choice.  I then converted my library to use intrusive_ptrs instead of shared_ptrs.  The source code for this library is attached as 'i_ptr_lib.cpp'.  When I tried to use this library from python, I found that the intrusive_ptr was not as well supported as the shared_ptr.  A sample ipython session is shown below:

 In [1]: from i_ptr_lib import *
 In [2]: c1 = C('c1'); c2 = C('c2')
 C(c1)
 C(c2)
 In [3]: c1.set_ptr(c2)
 -------------------------------
 Boost.Python.ArgumentError                             
 Traceback (most recent call last)
 ArgumentError: Python argument types in
     C.set_ptr(C, C)
 did not match C++ signature:
     set_ptr(C {lvalue}, boost::intrusive_ptr<C>)

It seems that I will need to supply all the necessary type conversions for the intrusive_ptr.  I was hoping to not have to dig that deep into boost python's template magic at this point...  Is there any good documentation on how to do this?  I found the following post that seemed promising:

 http://mail.python.org/pipermail/c++-sig/2007-February/011961.html

I downloaded the 'register_intrusive_ptr_from_python.hpp' and 'intrusive_ptr_from_python.hpp' file attachments from the above link and then modified my i_ptr_lib.cpp program by adding '#include register_intrusive_ptr_from_python.hpp' near the top, and this line after the end of the class_<C> declaration:

 boostPatch::register_intrusive_ptr_from_python_and_casts( (C *)0, class_<C>::metadata::bases() );

I then tested the library again:

 In [1]: from i2_ptr_lib import *
 In [2]: c1 = C('c1'); c2 = C('c2')
 C(c1)
 C(c2)
 In [3]: c1.set_ptr(c2)
 In [4]: del(c2)
 ~C(c2)
 In [5]: c1.get_ptr()
 ------------------------
 exceptions.TypeError      
 Traceback (most recent call last)
 TypeError: No to_python (by-value) converter found for C++ type: boost::intrusive_ptr<C>

The set_ptr method no longer generates an exception, but c2's lifetime is not tied to c1 like it is for the shared_ptr version (the destructor gets called when c2 gets deleted).  Another type conversion problem pops up in 'In[5]'.

Am I on the right path here?  Is it possible to make the intrusive_ptr work as seamlessly as the shared_ptr, or is there some fundamental problem with what I hope to achieve?  Is there some technical reason why this is not already implemented, or is it just that there hasn't been a need up to this point?  If I should continue down this path, can someone give me a skeleton of the code I need to write (and how to get it 'registered') to provide all the functionality that exists for the shared_ptr class?

Thanks again,

Jeff Webb


s_ptr_lib.cpp
--------------------------------------------------------------
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <iostream>

using namespace std;
using namespace boost;
using namespace boost::python;

class C;
typedef shared_ptr<C> CPtr;

class C
{
public:
 C(const string &s) : name(s)
 {
   cout << "C(" << name << ")" << endl;
 }
 ~C() 
 {
   cout << "~C(" << name << ")" << endl;
 }
 void set_ptr(CPtr ptr)
 {
   this->ptr = ptr;
 }
 void clear_ptr()
 {
   ptr.reset();
 }
 CPtr get_ptr()
 {
   return ptr;
 }
private:
 string name;
 CPtr ptr;
};

BOOST_PYTHON_MODULE(s_ptr_lib)
{
 class_<C>("C", init<const string&>())
   .def("set_ptr", &C::set_ptr)
   .def("clear_ptr", &C::set_ptr)
   .def("get_ptr", &C::get_ptr)
   ;
}



i_ptr_lib.cpp
--------------------------------------------------------------
#include <boost/python.hpp>
#include <boost/intrusive_ptr.hpp>
#include <string>
#include <iostream>

using namespace std;
using namespace boost;
using namespace boost::python;

class C;
typedef intrusive_ptr<C> CPtr;

namespace boost
{
 void intrusive_ptr_add_ref(C * ptr);
 void intrusive_ptr_release(C * ptr);
}

class C
{
public:
 C(const string &s) : name(s)
 {
   cout << "C(" << name << ")" << endl;
 }
 ~C() 
 {
   cout << "~C(" << name << ")" << endl;
 }
 void set_ptr(CPtr ptr)
 {
   this->ptr = ptr;
 }
 void clear_ptr()
 {
   intrusive_ptr_release((C*) ptr.get());
 }
 CPtr get_ptr()
 {
   return ptr;
 }
private:
 friend void boost::intrusive_ptr_add_ref(C * ptr);
 friend void boost::intrusive_ptr_release(C * ptr);

 string name;
 CPtr ptr;
 int ref_count;
};

namespace boost
{
 inline void intrusive_ptr_add_ref(C * ptr)
 {
   ++(ptr->ref_count);
 }

 void intrusive_ptr_release(C * ptr)
 {
   if (--(ptr->ref_count) == 0)
     delete ptr;
 }
}

BOOST_PYTHON_MODULE(i_ptr_lib)
{
class_<C>("C", init<const string&>())
   .def("set_ptr", &C::set_ptr)
   .def("clear_ptr", &C::set_ptr)
   .def("get_ptr", &C::get_ptr)
   ;
}





More information about the Cplusplus-sig mailing list