>>> from pynebula3.Core import CoreServer # C++ singleton object is created and embedded in a new Python object >>> coreServer = CoreServer.Create() >>> print coreServer <pynebula3.Core.CoreServer object at 0x00AB1340> # existing C++ singleton object is embedded in a new Python object >>> coreServer2 = CoreServer.Instance() >>> print coreServer2 <pynebula3.Core.CoreServer object at 0x00AB1378> >>> print coreServer2.AppName Nebula3 # the C++ singleton object embedded in the Python object is destroyed >>> coreServer = None # remaining Python object now contains a dangling pointer to the C++ singleton object >>> print coreServer2 <pynebula3.Core.CoreServer object at 0x00AB1378> >>> print coreServer2.AppName # crash!
The other candidate was the reference_existing_object policy, and this is the one that is usually suggested for use with singletons. Under this policy the C++ object embedded in a Python object is not deleted when the Python object is destroyed, you have to delete the C++ object explicitly. The problem is that you should never delete a reference counted Nebula object explicitly, you should use AddRef()/Release() to adjust the reference count, and it will be automatically deleted when the count hits zero. So you'll have to do something like this:
>>> from pynebula3.Core import CoreServer >>> coreServer = CoreServer.Create() >>> coreServer.AddRef() >>> print coreServer.AppName Nebula3 >>> coreServer.Release() >>> coreServer = None
Two lines to create an object and two lines to destroy it? No thanks! And so I set out to create my own policy. Essentially what I wanted is the behavior of the manage_new_object policy where the C++ object is deleted when the Python object is destroyed, but instead of embedding the C++ object itself I wanted it to embed a Nebula smart pointer to the C++ object. In fact manage_new_policy already embeds smart pointers instead of plain pointers, but they're not Nebula smart pointers. So all I had to do was take the manage_new_object policy and make it use Nebula smart pointers. Here is the new policy:
// return_by_smart_ptr.h #include <boost/python/detail/prefix.hpp> #include <boost/python/detail/indirect_traits.hpp> #include <boost/mpl/if.hpp> #include <boost/python/to_python_indirect.hpp> #include <boost/type_traits/composite_traits.hpp> #include "pynebula3/foundation/core/pointee.h" namespace boost { namespace python { namespace detail { // attempting to instantiate this type will result in a compiler error, // if that happens it means you're trying to use return_by_smart_pointer // on a function/method that doesn't return a pointer! template <class R> struct return_by_smart_ptr_requires_a_pointer_return_type # if defined(__GNUC__) && __GNUC__ >= 3 || defined(__EDG__) {} # endif ; // this is where all the work is done, first the plain pointer is // converted to a smart pointer, and then the smart pointer is embedded // in a Python object struct make_owning_smart_ptr_holder { template <class T> static PyObject* execute(T* p) { typedef Ptr<T> smart_pointer; typedef objects::pointer_holder<smart_pointer, T> holder_t; smart_pointer ptr(const_cast<T*>(p)); return objects::make_ptr_instance<T, holder_t>::execute(ptr); } }; } // namespace detail struct return_by_smart_ptr { template <class T> struct apply { typedef typename mpl::if_c< boost::is_pointer<T>::value, to_python_indirect<T, detail::make_owning_smart_ptr_holder>, detail::return_by_smart_ptr_requires_a_pointer_return_type<T> >::type type; }; }; }} // namespace boost::pythonUsing the Core::CoreServer again as an example, I can bind it like this:
namespace bp = boost::python; bp::class_<Core::CoreServer, Ptr<Core::CoreServer>, boost::noncopyable>("CoreServer", bp::no_init) .def("Create", &Core::CoreServer::Create, bp::return_value_policy<bp::return_by_smart_ptr>()) .staticmethod("Create") .def("HasInstance", &Core::CoreServer::HasInstance) .staticmethod("HasInstance") .def("Instance", &Core::CoreServer::Instance, bp::return_value_policy<bp::return_by_smart_ptr>()) .staticmethod("Instance") .add_property("AppName", bp::make_function(&Core::CoreServer::GetAppName, bp::return_value_policy<bp::return_by_value>()), bp::make_function(&Core::CoreServer::SetAppName) )) // etc. ;
And then use it in Python:
>>> from pynebula3.Core import CoreServer # singleton instance is created and stored in a new Ptr<CoreServer> instance # which is in turn embedded in a new Python object >>> coreServer = CoreServer.Create() >>> print coreServer <pynebula3.Core.CoreServer object at 0x00AB1340> # existing singleton instance is stored in a new Ptr<CoreServer> instance # which is embedded in a new Python object >>> coreServer2 = CoreServer.Instance() >>> print coreServer2 <pynebula3.Core.CoreServer object at 0x00AB1378> >>> print coreServer2.AppName Nebula3 # the Ptr<CoreServer> instance embedded in the Python object is deleted, # the singleton instance itself stays alive because another Ptr<CoreServer> # instance still references it >>> coreServer = None >>> print coreServer2 <pynebula3.Core.CoreServer object at 0x00AB1378> >>> print coreServer2.AppName Nebula3 # the remaining Ptr<CoreServer> instance embedded in the Python object is deleted, # since no other Ptr<CoreServer> instances reference the singleton instance it # too is deleted >>> coreServer2 = None
And that's all there is to it, though it remains to be seen how well it all works in practice.
No comments:
Post a Comment