3

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

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

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

4

1 回答 1

1

使用 Boost.Python,这可以通过使用boost::shared_ptr来管理对象的生命周期来实现。这通常通过HeldType在暴露 C++ 类型时指定boost::python::class_. 但是,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()调用之外。

于 2013-05-30T20:10:34.527 回答