While embedding python in my application I've faced with problem related to python objects lifetime. My application exports some classes with virtual methods to python, so they can be derived and extended by python code. Application is using python interpreter and calls virtual methods of objects. The problem is that when object's reference counter reaches zero inside of python overridden method, which was called from c++ code, interpreter destroys object immediately. So, if we call such method inside another method of object we will get behavior equivalent to delete this statement. Simple test code:
Object:
class Base
{
public:
virtual ~Base()
{
std::cout << "C++ deleted" << std::endl;
std::cout.flush();
}
virtual void virtFunc()
{
}
void rmFunc()
{
std::cout << "Precall" << std::endl;
virtFunc();
std::cout << "Postcall" << std::endl;
//Segfault here, this doesn't exists.
value = 0;
}
private:
int value;
};
Boost::Python module library:
#include <boost/python.hpp>
#include <list>
#include "Types.h"
#include <iostream>
// Policies used for reference counting
struct add_reference_policy : boost::python::default_call_policies
{
static PyObject *postcall(PyObject *args, PyObject *result)
{
PyObject *arg = PyTuple_GET_ITEM(args, 0);
Py_INCREF(arg);
return result;
}
};
struct remove_reference_policy : boost::python::default_call_policies
{
static PyObject *postcall(PyObject *args, PyObject *result)
{
PyObject *arg = PyTuple_GET_ITEM(args, 0);
Py_DecRef(arg);
return result;
}
};
struct BaseWrap: Base, boost::python::wrapper<Base>
{
BaseWrap(): Base()
{
}
virtual ~BaseWrap()
{
std::cout << "Wrap deleted" << std::endl;
std::cout.flush();
}
void virtFunc()
{
if (boost::python::override f = get_override("virtFunc"))
{
try
{
f();
}
catch (const boost::python::error_already_set& e)
{
}
}
}
void virtFunc_()
{
Base::virtFunc();
}
};
std::list<Base*> objects;
void addObject(Base *o)
{
objects.push_back(o);
}
void removeObject(Base *o)
{
objects.remove(o);
}
BOOST_PYTHON_MODULE(pytest)
{
using namespace boost::python;
class_<BaseWrap, boost::noncopyable>("Base", init<>())
.def("virtFunc", &Base::virtFunc, &BaseWrap::virtFunc_);
def("addObject", &addObject, add_reference_policy());
def("removeObject", &removeObject, remove_reference_policy());
}
Application, linked with module:
#include <boost/python.hpp>
#include <list>
#include "Types.h"
extern std::list<Base*> objects;
int main(int argc, char **argv)
{
Py_Initialize();
boost::python::object main_module = boost::python::import("__main__");
boost::python::object main_namespace = main_module.attr("__dict__");
try
{
boost::python::exec_file("fail-test.py", main_namespace);
}
catch(boost::python::error_already_set const &)
{
PyErr_Print();
}
sleep(1);
objects.front()->rmFunc();
sleep(1);
}
fail-test.py:
import pytest
class Derived(pytest.Base):
def __init__(self, parent):
pytest.Base.__init__(self)
pytest.addObject(self)
def __del__(self):
print("Python deleted")
def virtFunc(self):
pytest.removeObject(self)
o1 = Derived(None)
o1 = None
Output:
Precall
Python deleted
Wrap deleted
C++ deleted
Postcall
Is there any good way to avoid such behavior?