Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
173 views
in Technique[技术] by (71.8m points)

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

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.)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...