【问题标题】:C++ destructor calling of boost::python wrapped objectsboost::python 包装对象的 C++ 析构函数调用
【发布时间】:2016-11-08 18:01:15
【问题描述】:

当 A 的 C++ 析构函数时,boost::python 是否提供任何保证? 考虑到达到零的时刻,调用被包裹的对象 对应的python对象的引用计数?

我担心一个 C++ 对象,它打开一个文件进行写入并在其析构函数中执行文件关闭。当所有对对象的python引用都被删除或超出范围时,是否保证文件被写入?

我的意思是:

A=MyBoostPythonObject()
del A # Is the C++ destructor of MyBoostPythonObject called here?

我的经验表明,此时总是调用析构函数,但找不到任何保证。

【问题讨论】:

标签: python c++ boost-python


【解决方案1】:

Boost.Python 保证如果 Python 对象拥有被包装的 C++ 对象的所有权,那么当 Python 对象被删除时,被包装的 C++ 对象也将被删除。 Python 对象的生命周期由 Python 规定,其中当对象的引用计数达到零时,对象可能立即被销毁。对于非简单的情况,例如循环引用,对象将由垃圾收集器管理,并且可能在程序退出之前被销毁。

一个 Pythonic 解决方案可能是公开一个实现 context manager protocol 的类型。内容管理器协议由一对方法组成:一个在进入运行时上下文时调用,另一个在退出运行时上下文时调用。通过使用上下文管理器,可以控制文件打开的范围。

>>> with MyBoostPythonObject() as A:  # opens file.
...     A.write(...)                  # file remains open while in scope.
...                                   # A destroyed once context's scope is exited.

这是一个示例 demonstrating 将 RAII 类型的类作为上下文管理器暴露给 Python:

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

// Legacy API.
struct spam
{
  spam(int x)    { std::cout << "spam(): " << x << std::endl;   }
  ~spam()        { std::cout << "~spam()" << std::endl;         }
  void perform() { std::cout << "spam::perform()" << std::endl; }
};

/// @brief Python Context Manager for the Spam class.
class spam_context_manager
{
public:

  spam_context_manager(int x): x_(x) {}

  void perform() { return impl_->perform(); }

// context manager protocol
public:

  // Use a static member function to get a handle to the self Python
  // object.
  static boost::python::object enter(boost::python::object self)
  {
    namespace python = boost::python;
    spam_context_manager& myself =
      python::extract<spam_context_manager&>(self);

    // Construct the RAII object.
    myself.impl_ = std::make_shared<spam>(myself.x_);

    // Return this object, allowing caller to invoke other
    // methods exposed on this class.
    return self;
  }

  bool exit(boost::python::object type,
            boost::python::object value,
            boost::python::object traceback)
  {
    // Destroy the RAII object.
    impl_.reset();
    return false; // Do not suppress the exception.
  }
private:
  std::shared_ptr<spam> impl_;
  int x_;
};


BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<spam_context_manager>("Spam", python::init<int>())
    .def("perform", &spam_context_manager::perform)
    .def("__enter__", &spam_context_manager::enter)
    .def("__exit__", &spam_context_manager::exit)
    ;
}

互动使用:

>>> import example
>>> with example.Spam(42) as spam:
...     spam.perform()
...
spam(): 42
spam::perform()
~spam()

【讨论】:

  • 有什么好的方法可以防止用户只是做:spam = example.Spam(42)而无意泄露?
  • @SamKellett 考虑使用 RAII 类来管理底层资源,并将资源获取推迟到 __enter__ 而不是类构造函数。在上面的示例中,底层资源仅在Spam.__enter__() 内获取,并且由于使用std::shared_ptr,资源在Spam.__exit__ 或Python 销毁spam 对象时显式释放。
  • 但这不会阻止用户执行spam = example.Spam(42),对吗?它不会只是给他们一个悬空引用/ nullptr 吗?
  • @Sam 它不会阻止用户这样做。代理函数需要在调用委托之前实现任何需要的错误处理或延迟初始化。
猜你喜欢
  • 2013-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-02
  • 2021-06-30
  • 2013-01-26
相关资源
最近更新 更多