2013-05-25 27 views
3

在我的应用程序中嵌入python时,我遇到了与python对象生存期有关的问题。我的应用程序使用虚拟方法将一些类扩展为python,因此它们可以通过python代码派生和扩展。应用程序使用python解释器并调用对象的虚拟方法。问题是当对象的引用计数器在从C++代码调用的python覆盖方法内达到零时,解释器立即销毁对象。所以,如果我们在另一个对象方法中调用这样的方法,我们将得到相当于的行为,删除这个声明。简单的测试代码:Boost :: 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 

是否有避免这种行为没有什么好的办法?

回答

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()呼叫。