【问题标题】:Make Boost Python not delete the C++ object in destructor使 Boost Python 不删除析构函数中的 C++ 对象
【发布时间】:2013-01-16 12:35:09
【问题描述】:

我正在使用 Boost Python 为 wxWidgets 的一个子集创建绑定。 wxWidgets 中的窗口对象不应手动删除,因为它们自己处理删除操作:例如,当用户单击关闭按钮关闭顶层窗口时,它会自动删除自身。如果一个窗口删除,事件处理程序等会发生奇怪的事情。

(详情:http://docs.wxwidgets.org/2.8/wx_windowdeletionoverview.html

然而,这会导致在 Python 中创建的窗口对象出现问题:在垃圾回收时,C++ 对象总是被删除!

有什么方法可以告诉 Boost Python 不要拥有它创建的 C++ 对象的所有权?可能类似于构造函数的调用策略?

(另外,我有点担心如何处理从 C++ 中删除的对象。当关联的 C++ 对象被删除时,Python 对象会发生什么情况?Python 不会以任何方式得到通知。)

【问题讨论】:

    标签: wxwidgets boost-python


    【解决方案1】:

    其实使用 auto_ptr 有一个更简单的解决方案,它甚至在 Boost Python FAQ 中。 Python Wiki 有更多详细信息。到目前为止,它运行良好。

    【讨论】:

    • 请避免使用 auto_ptr
    【解决方案2】:

    这可以通过 boost::shared_ptr 和 Boost.Python 中的一些设置来完成。

    boost::shared_ptr 具有接受自定义删除器的构造函数。当shared_ptr 的引用计数达到零时,shared_ptr 将调用客户删除器,将先前托管的指针作为参数传递。这允许使用不同的释放策略,例如“无操作”或调用 wxWindow::Destroy() 的策略。

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

    当向 Python 公开一个类时,Boost.Python 允许通过 HeldType 管理类型。在这种情况下,HeldType 将是 boost::shared_ptr。这允许在 C++ 和 Python 之间发生正确的引用计数,并允许自定义解除分配策略。

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

    让这些透明地协同工作的诀窍是:

    • 禁止 Boost.Python 创建默认初始化程序 (__init__)。
    • 显式提供一个__init__ 函数,该函数将调用一个工厂函数,该函数返回一个带有自定义删除器的shared_ptr

    这是一个模拟的 window 类,它只能通过 destroy() 成员函数来销毁。

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

    定义了一个工厂函数,它将使用自定义删除器创建一个引用计数 window 对象。当引用计数达到零时,自定义删除器将在对象上调用 destroy()

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

    最后,使用 Boost.Python 公开 window 类,但抑制默认初始化程序,并透明地将其替换为 create_window 工厂函数。

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

    这是一个完整的例子:

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

    及其用法:

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

    请注意 Python 如何仅在 Python 不再引用该实例时才通知 C++ 删除该对象。因此,在这种情况下,Python 不会尝试与已删除的对象进行交互。希望这能减轻在​​ C++ 中删除对象时表达的担忧。

    如果在某些情况下 C++ 将删除 Python 中仍处于活动状态的对象,请考虑使用 opaque pointer 来分隔实现类和句柄类。句柄类可以在转发调用之前检查关联的实现实例是否已被删除,从而允许向 Python 抛出异常。

    【讨论】:

    • 哇,谢谢,那里有很多好主意。我想出了另一个解决方案,为每个类创建一个静态工厂方法,该方法返回一个带有 return_value_policy reference_existing_object 的对象。但你的解决方案更好。
    • 我已经用抽象类尝试过这种方法,但我发现 make_constructor 似乎不起作用。如果我使用 init 构造函数,它可以工作,但如果我使用 make_constructor,则在 Python 和 C++ 之间来回传递时,虚拟方法的实现会丢失。由于无法使用 init 设置自定义析构函数,因此我从 shared_ptr 派生了一个自定义的 shared_ptr 类,该类始终具有自定义析构函数。但我不确定从 shared_ptr 派生它是否安全。你怎么看?
    • 我会犹豫是否从 shared_ptr 派生。从概念上讲,以非多态方式使用时应该没问题。然而,Boost.Python 对shared_ptr 进行了大量的元编程和处理,所以我不能肯定地说它是安全的。最初的问题听起来像是对象切片的结果。我希望 Boost.Python 解决方案可以混合使用 baseswrapper
    猜你喜欢
    • 2021-04-10
    • 2016-11-08
    • 2013-05-21
    • 2013-09-30
    • 2012-04-30
    • 1970-01-01
    • 2021-11-05
    • 2021-04-12
    • 2015-01-10
    相关资源
    最近更新 更多