Make Boost Python not delete the C++ object in destructor

牧云@^-^@ 提交于 2019-11-28 10:40:09

问题


I'm creating bindings for a subset of wxWidgets using Boost Python. Window objects in wxWidgets should not be deleted manually since they handle their own deletion: for example, when a top level window is closed by the user clicking the close button it automatically deletes itself. If a window is deleted strange things will happen with event handlers etc.

(Details: http://docs.wxwidgets.org/2.8/wx_windowdeletionoverview.html)

This however leads to a problem with window objects created in Python: on garbage collection the C++ object is always deleted!

Is there any way to tell Boost Python to not take ownership of C++ objects it creates? Something like a call policy for the constructor perhaps?

(Also, I'm a little bit worried about how to handle objects deleted from C++. What should happen to the Python object when an associated C++ object is deleted? Python will not get notified about this in any way.)


回答1:


This can be accomplished with a boost::shared_ptr and some setup in Boost.Python.

boost::shared_ptr has constructors that accept a custom deleter. When the shared_ptr's reference count reaches zero, the shared_ptr will invoke the customer deleter, passing the previously managed pointer as an argument. This allows for different deallocation strategies to be used, such as a "no-op", or one that invokes wxWindow::Destroy().

class window {};
void no_op(window*) {};
boost::shared_ptr(new window(), &no_op);

When exposing a class to Python, Boost.Python allows for types to be managed via a HeldType. In this case, the HeldType will be boost::shared_ptr. This allows for proper referencing counting to occur between C++ and Python, and allows for the custom deallocation strategy.

boost::python::class_<window, boost::shared_ptr<window>, ...>("Window", ...);

The trick to getting these to work together transparently is to:

  • Suppress Boost.Python from creating a default initializer (__init__).
  • Explicitly provide an __init__ function that will invoke a factory function returning a shared_ptr with a custom deleter.

Here is the a mocked up window class that is intended to only be destroyed through the destroy() member function.

class window
{
...
private:
  ~window();
public:
  void destroy();
};

A factory function is defined that will create a reference counted window object with a custom deleter. The custom deleter will invoke destroy() on the object when the reference count reaches zero.

boost::shared_ptr<window> create_window()
{
  return boost::shared_ptr<window>(
    new window(),
    boost::mem_fn(&window::destroy));
}

Finally, use Boost.Python to expose the window class, but suppress the default initializer, and transparently replace it with the create_window factory function.

boost::python::class_<window, boost::shared_ptr<window>, 
                      boost::noncopyable>("Window", python::no_init)
  .def("__init__", python::make_constructor(&create_window));

Here is a complete example:

#include <iostream>
#include <boost/mem_fn.hpp>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>

/// @brief Mockup window class.
class window
{
public:
  window(unsigned int id)
    : id_(id)
  {
    std::cout << "window::window() " << id_ << std::endl;
  }
  void action() { std::cout << "window::action() " << id_ << std::endl; }
  void destroy()
  {
    std::cout << "window::destroy() " << id_ << std::endl;
    delete this;
  }
private:
  ~window() { std::cout << "window::~window() " << id_ << std::endl; }
private:
  unsigned int id_;
};

/// @brief Factory function that will create reference counted window
///        objects, that will call window::destroy() when the reference
///        count reaches zero.
boost::shared_ptr<window> create_window(unsigned int id)
{
  return boost::shared_ptr<window>(
    new window(id),
    boost::mem_fn(&window::destroy));
}

BOOST_PYTHON_MODULE(example) {
  namespace python = boost::python;
  // Expose window, that will be managed by shared_ptr, and transparently
  // constructs the window via a factory function to allow for a custom
  // deleter.
  python::class_<window, boost::shared_ptr<window>, 
                 boost::noncopyable>("Window", python::no_init)
    .def("__init__", python::make_constructor(&create_window))
    .def("action", &window::action)
    ;
}

And its usage:

>>> from example import Window
>>> w1 = Window(1)
window::window() 1
>>> w2 = Window(2)
window::window() 2
>>> w3 = Window(3)
window::window() 3
>>> del w2
window::destroy() 2
window::~window() 2
>>> w3 = None
window::destroy() 3
window::~window() 3
>>> w = w1
>>> del w1
>>> w.action()
window::action() 1
>>> w = None
window::destroy() 1
window::~window() 1

Notice how Python only informs C++ to delete the object once Python no longer has a reference to the instance. Thus, in this scenario, Python will not try to interact on an object that has been deleted. Hopefully this alleviates concerns expressed when an object is deleted in C++.

If there are situations where C++ will be deleting objects that are still active in Python, then consider using an opaque pointer to separate the implementation class and the handle class. The handle class could check if the associated implementation instance has been deleted before forwarding the call, allowing an exception to be thrown up to Python.




回答2:


Actually there is a much simpler solution by using auto_ptr, and it's even in the Boost Python FAQ. There are more details on the Python Wiki. So far its working perfectly.



来源:https://stackoverflow.com/questions/14642216/make-boost-python-not-delete-the-c-object-in-destructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!