Boost :: python:object在重写方法中销毁自身

时间:2013-05-25 16:14:58

标签: c++ python boost-python

在我的应用程序中嵌入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

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

1 个答案:

答案 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()来电之后。