2013-05-25 52 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()呼叫。