在我的应用程序中嵌入python时,我遇到了与python对象生命周期相关的问题。我的应用程序使用虚拟方法将一些类扩展为python,因此可以通过python代码派生和扩展它们。应用程序使用python解释器并调用对象的虚方法。问题是当对象的引用计数器在从c ++代码调用的python重写方法内部达到零时,解释器会立即销毁对象。因此,如果我们在另一个对象方法中调用此方法,我们将获得等同于删除此语句的行为。简单的测试代码:
对象:
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模块库:
#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());
}
应用程序,与模块链接:
#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
输出:
Precall
Python deleted
Wrap deleted
C++ deleted
Postcall
有没有什么好方法可以避免这种行为?
答案 0 :(得分:1)
使用Boost.Python,可以使用boost::shared_ptr
来管理对象的生命周期。这通常是通过boost::python::class_
公开C ++类型时指定HeldType
来完成的。但是,Boost.Python通常使用boost::shared_ptr
提供所需的功能。在这种情况下,boost::python::wrapper
类型支持转化。
这是一个完整的例子:
#include <iostream>
#include <list>
#include <string>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
class Base
{
public:
virtual ~Base() { std::cout << "C++ deleted" << std::endl; }
virtual void virtFunc() {}
void rmFunc()
{
std::cout << "Precall" << std::endl;
virtFunc();
std::cout << "Postcall" << std::endl;
}
};
/// @brief Wrap Base to allow for python derived types to override virtFunc.
struct BaseWrap
: Base,
boost::python::wrapper<Base>
{
virtual ~BaseWrap() { std::cout << "Wrap deleted" << std::endl; }
void virtFunc_() { Base::virtFunc(); }
void virtFunc()
{
namespace python = boost::python;
if (python::override f = get_override("virtFunc"))
{
try { f(); }
catch (const python::error_already_set&) {}
}
}
};
std::list<boost::shared_ptr<Base> > objects;
void addObject(boost::shared_ptr<Base> o) { objects.push_back(o); }
void removeObject(boost::shared_ptr<Base> o) { objects.remove(o); }
BOOST_PYTHON_MODULE(pytest)
{
namespace python = boost::python;
python::class_<BaseWrap, boost::noncopyable >("Base", python::init<>())
.def("virtFunc", &Base::virtFunc, &BaseWrap::virtFunc_);
python::def("addObject", &addObject);
python::def("removeObject", &removeObject);
}
const char* derived_example_py =
"import pytest\n"
"\n"
"class Derived(pytest.Base):\n"
" def __init__(self, parent):\n"
" pytest.Base.__init__(self)\n"
" pytest.addObject(self)\n"
"\n"
" def __del__(self):\n"
" print(\"Python deleted\")\n"
"\n"
" def virtFunc(self):\n"
" pytest.removeObject(self)\n"
"\n"
"o1 = Derived(None)\n"
"o1 = None\n"
;
int main()
{
PyImport_AppendInittab("pytest", &initpytest);
Py_Initialize();
namespace python = boost::python;
python::object main_module = python::import("__main__");
python::object main_namespace = main_module.attr("__dict__");
try
{
exec(derived_example_py, main_namespace);
}
catch (const python::error_already_set&)
{
PyErr_Print();
}
boost::shared_ptr<Base> o(objects.front());
o->rmFunc();
std::cout << "pre reset" << std::endl;
o.reset();
std::cout << "post reset" << std::endl;
}
输出:
Precall
Postcall
pre reset
Python deleted
Wrap deleted
C++ deleted
post reset
需要注意的最后一个变化是:
objects.front()->rmFunc();
替换为:
boost::shared_ptr<Base> o(objects.front());
o->rmFunc();
这是必需的,因为std::list::front
返回对元素的引用。通过创建shared_ptr
的副本,生命周期延长到rmFunc()
来电之后。