While playing with Boost.Python and C++, sometimes we create classes that are bound using the class itself and a boost::shared_ptr<>
version. This is very convenient for many reasons and can be used in lots of places. However, the mechanism does not seem to work reliably when boost::python
returns a boost::shared_ptr<>
to a value that was produced in Python and is recorded on a C++ static variable.
Normally, I'd expect the returned boost::shared_ptr<>
to hold a special deleter that would take care of this, but that does not seem to be the case. What seems to happen is that the returned boost::shared_ptr
just wraps a pointer to a value produced in Python w/o any special consideration with deletion. This results in a consistent crash coming from a double-deletion (one from the Python interpreter itself and one from the C++ static) - or at least it looks like it.
To reproduce this behavior using the code below, create a test.cc
file like below and test with the following script.
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
struct A {
std::string _a;
A(std::string a): _a(a) {}
std::string str() { return _a; }
};
static boost::shared_ptr<A> holder(new A("foo"));
static void set_holder(boost::shared_ptr<A> a_ptr) {
holder = a_ptr;
}
static boost::shared_ptr<A> get_holder() {
return holder;
}
BOOST_PYTHON_MODULE(test)
{
using namespace boost::python;
class_<A, boost::shared_ptr<A> >("A", init<std::string>())
.def("__str__", &A::str)
;
def("set_holder", &set_holder);
def("get_holder", &get_holder);
}
With the following Python test program:
import test
print(str(test.get_holder()))
test.set_holder(test.A('bar'))
print(str(test.get_holder()))
Compiling (with g++ -I/usr/include/python2.7 -shared -fpic test.cc -lboost_python -lpython2.7 -o test.so
) and running the above program (python test.py
) under Linux (ubuntu 12.10, with Python 2.7 and Boost 1.50), resulted in the following stack trace:
#0 0x000000000048aae8 in ?? ()
#1 0x00007fa44f85f589 in boost::python::converter::shared_ptr_deleter::operator()(void const*) () from /usr/lib/libboost_python-py27.so.1.50.0
#2 0x00007fa44fa97cf9 in boost::detail::sp_counted_impl_pd<void*, boost::python::converter::shared_ptr_deleter>::dispose() ()
from /remote/filer.gx/home.active/aanjos/test.so
#3 0x00007fa44fa93f9c in boost::detail::sp_counted_base::release() ()
from /remote/filer.gx/home.active/aanjos/test.so
#4 0x00007fa44fa9402b in boost::detail::shared_count::~shared_count() ()
from /remote/filer.gx/home.active/aanjos/test.so
#5 0x00007fa44fa94404 in boost::shared_ptr<A>::~shared_ptr() ()
from /remote/filer.gx/home.active/aanjos/test.so
#6 0x00007fa450337901 in __run_exit_handlers (status=0,
listp=0x7fa4506b46a8 <__exit_funcs>, run_list_atexit=true) at exit.c:78
#7 0x00007fa450337985 in __GI_exit (status=<optimized out>) at exit.c:100
#8 0x00007fa45031d774 in __libc_start_main (main=0x44b769 <main>, argc=2,
ubp_av=0x7fffaa28e2a8, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffaa28e298) at libc-start.c:258
#9 0x00000000004ce6dd in _start ()
This indicates a double-deletion has occured at the static destructor. This behavior seems to be consistent among different platforms.
Question: is it possible to achieve the described behavior w/o copying the returned valued from boost::python
? In the above toy example, that would be simple, but in my real problem, a deep-copy of A
would be impractical.
The problem you have is the destruction of the shared_ptr happens after python has finalized. Look at:
I suggest to encapsulate the shared_ptr, which comes without extra cleanup code. Four soulutions, though:
The python file:
The output of the test is: