【问题标题】:Boost::python: object destroys itself inside a overriden methodBoost::python:对象在被覆盖的方法中自行销毁
【发布时间】:2013-05-25 16:14:57
【问题描述】:

在我的应用程序中嵌入 python 时,我遇到了与 python 对象生命周期相关的问题。我的应用程序将一些带有虚拟方法的类导出到 python,因此它们可以通过 python 代码进行派生和扩展。应用程序正在使用 python 解释器并调用对象的虚拟方法。问题是当对象的引用计数器在从 c++ 代码调用的 python 覆盖方法中达到零时,解释器会立即销毁对象。因此,如果我们在对象的另一个方法中调用这样的方法,我们将获得等同于 delete this 语句的行为。简单的测试代码:

对象:

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);
}

失败测试.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

有什么好的方法可以避免这种行为吗?

【问题讨论】:

    标签: c++ python boost-python


    【解决方案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() 调用之外的生命周期。

    【讨论】:

      猜你喜欢
      • 2022-11-25
      • 2012-02-18
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      • 2019-02-08
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      相关资源
      最近更新 更多