Boost :: python: l’object si distrugge all’interno di un metodo sovrascritto

Pur incorporando python nella mia applicazione ho affrontato il problema relativo alla durata degli oggetti python. La mia applicazione espande alcune classi con metodi virtuali per Python, in modo che possano essere derivate ed estese dal codice Python. L’applicazione utilizza l’interprete python e chiama i metodi virtuali degli oggetti. Il problema è che quando il contatore di riferimento dell’object raggiunge lo zero all’interno del metodo sovrascritto python, che è stato chiamato dal codice c ++, l’interprete distrugge immediatamente l’object. Quindi, se chiamiamo tale metodo all’interno di un altro metodo di object, otterremo un comportamento equivalente a cancellare questa affermazione. Semplice codice di prova:

Oggetto:

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 :: Libreria dei moduli Python:

 #include  #include  #include "Types.h" #include  // 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 { 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 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_("Base", init()) .def("virtFunc", &Base::virtFunc, &BaseWrap::virtFunc_); def("addObject", &addObject, add_reference_policy()); def("removeObject", &removeObject, remove_reference_policy()); } 

Applicazione, collegata con il modulo:

 #include  #include  #include "Types.h" extern std::list 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 

Produzione:

 Precall Python deleted Wrap deleted C++ deleted Postcall 

C’è un buon modo per evitare questo comportamento?

Con Boost.Python, questo può essere ottenuto utilizzando boost::shared_ptr per gestire la durata degli oggetti. Normalmente questo viene fatto specificando HeldType quando HeldType il tipo C ++ tramite boost::python::class_ . Tuttavia, Boost.Python fornisce spesso la funzionalità desiderata con boost::shared_ptr . In questo caso, il tipo boost::python::wrapper supporta le conversioni.


Ecco un esempio completo:

 #include  #include  #include  #include  #include  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 { 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 > objects; void addObject(boost::shared_ptr o) { objects.push_back(o); } void removeObject(boost::shared_ptr o) { objects.remove(o); } BOOST_PYTHON_MODULE(pytest) { namespace python = boost::python; python::class_("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 o(objects.front()); o->rmFunc(); std::cout << "pre reset" << std::endl; o.reset(); std::cout << "post reset" << std::endl; } 

E l'output:

 Precall Postcall pre reset Python deleted Wrap deleted C++ deleted post reset 

Un'ultima modifica da notare è che:

 objects.front()->rmFunc(); 

è stato sostituito con:

 boost::shared_ptr o(objects.front()); o->rmFunc(); 

Questo è stato necessario perché std::list::front restituisce un riferimento all'elemento. Creando una copia di shared_ptr , la durata della vita viene estesa oltre la chiamata rmFunc() .