C++ destructor calling of boost::python wrapped objects
Question: Does boost::python provide any guarantee when the C++ destructor of a wrapped
object is called considering the moment of reaching the zero reference count
of the corresponding python object?

I am concerned about a C++ object that opens a file for writing and performs
the file closing in its destructor. Is it guaranteed that the file is written
when all python references to the object are deleted or out of scope?

I mean:

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

My experience suggests that the destructor is always called at this point but
could not find any guarantee for this.


Answer: Boost.Python makes the guarantee that if the Python object has ownership of
the wrapped C++ object, then when the Python object is deleted, the wrapped
C++ object will be deleted. The Python object's lifetime is dictated by
Python, wherein when an object’s reference count reaches zero, the object
_may_ be immediately destroyed. For non-simplistic cases, such as cyclic
references, the objects will be managed by the garbage collector, and _may_ be
destroyed before the program exits.

One Pythonic solution may be to expose a type that implements the [context
manager protocol](https://docs.python.org/2/library/stdtypes.html#context-
manager-types). The content manager protocol is made up of a pair of methods:
one which will be invoked when entering a runtime context, and one which will
be invoked when exiting a runtime context. By using a context manager, one
could control the scope in which a file is open.

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

* * *

Here is an example [demonstrating](http://coliru.stacked-
crooked.com/a/245d68e4bcea05f0) exposing a RAII-type class to Python as a
context manager:

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

Interactive usage:

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

