python c++ c++11 unique-ptr boost-python

Boost.Python: Cómo exponer std:: unique_ptr



c++ c++11 (4)

Boost soporta movable semantics y unique_ptr desde v.1.55 . Pero en mi proyecto utilicé una versión anterior e hice un envoltorio tan simple:

class_<unique_ptr<HierarchyT>, noncopyable>(typpedName<LinksT>("hierarchy", false) , "hierarchy holder") .def("__call__", &unique_ptr<HierarchyT>::get, return_internal_reference<>(), "get holding hierarchy") .def("reset", &unique_ptr<HierarchyT>::reset, "reset holding hierarhy") ;

para crear unique_ptr<HierarchyT> como shierarchy Python y pasarlo a la función que lo acepte como referencia.
Código Python:

hier = mc.shierarchy() mc.clusterize(hier, nds)

donde la función C ++ es float clusterize(unique_ptr<HierarchyT>& hier,...) .
Luego, para acceder a los resultados en Python, haga una llamada a hier() para obtener el objeto envuelto de unique_ptr:

output(hier(), nds)

Soy bastante nuevo en boost.python y trato de exponer el valor de retorno de una función a python.

La firma de la función se ve así:

std::unique_ptr<Message> someFunc(const std::string &str) const;

Al llamar a la función en Python, me aparece el siguiente error:

TypeError: No to_python (by-value) converter found for C++ type: std::unique_ptr<Message, std::default_delete<Message> >

Mi llamada de función en python se ve así:

a = mymodule.MyClass() a.someFunc("some string here") # error here

Intenté exponer std :: unique_ptr pero no puedo hacer que funcione. ¿Alguien sabe cómo exponer correctamente la clase de puntero? ¡Gracias!

Edit: he intentado lo siguiente:

class_<std::unique_ptr<Message, std::default_delete<Message>>, bost::noncopyable ("Message", init<>()) ;

Este ejemplo compila, pero sigo recibiendo el error mencionado anteriormente. Además, traté de exponer la clase Message sí.

class_<Message>("Message", init<unsigned>()) .def(init<unsigned, unsigned>()) .def("f", &Message::f) ;


Creo que hoy en día no hay forma de hacer lo que está buscando ... La razón es porque std::unique_ptr<Message> someFunc(const std::string &str) está devolviendo por valor, lo que significa una de dos cosas:

  1. El valor de retorno se copiará (pero unique_ptr no se puede copiar );
  2. El valor de retorno se moverá (ahora el problema es que boost :: python no proporciona soporte para mover la semántica). (heyy, estoy usando boost 1,53, no estoy seguro en las versiones más recientes);

Es someFunc () creando el objeto? En caso de que SÍ, creo que la solución es crear una envoltura, en caso de que NO, puede devolver por referencia:

std::unique_ptr<Message>& someFunc(const std::string &str)

exponer la clase:

class_<std::unique_ptr<Message, std::default_delete<Message>>, boost::noncopyable>("unique_ptr_message") .def("get", &std::unique_ptr<Message>::get, return_value_policy<reference_existing_object>()) ;

y también las funciones:

def("someFunc", someFunc, return_value_policy<reference_existing_object>());


En resumen, Boost.Python no admite la semántica de movimientos y, por lo tanto, no admite std::unique_ptr . El registro de noticias / cambios de Boost.Python no tiene indicios de que se haya actualizado para C ++ 11 move-semantics. Además, esta solicitud de función para el soporte de unique_ptr no se ha tratado durante más de un año.

Sin embargo, Boost.Python admite la transferencia de propiedad exclusiva de un objeto hacia y desde Python a través de std::auto_ptr . Como unique_ptr es esencialmente una versión más segura de auto_ptr , debería ser bastante sencillo adaptar una API usando unique_ptr a una API que use auto_ptr :

  • Cuando C ++ transfiere la propiedad a Python, la función C ++ debe:
  • Cuando Python transfiere la propiedad a C ++, la función C ++ debe:
    • aceptar la instancia a través de auto_ptr . Las FAQ mencionan que los punteros devueltos desde C ++ con una política manage_new_object se administrarán a través de std::auto_ptr .
    • tener auto_ptr liberar el control a un unique_ptr vía release()

Dada una API / biblioteca que no se puede cambiar:

/// @brief Mockup Spam class. struct Spam; /// @brief Mockup factory for Spam. struct SpamFactory { /// @brief Create Spam instances. std::unique_ptr<Spam> make(const std::string&); /// @brief Delete Spam instances. void consume(std::unique_ptr<Spam>); };

SpamFactory::make() y SpamFactory::consume() deben ajustarse mediante funciones auxiliares.

Las funciones que transfieren la propiedad de C ++ a Python pueden ser envueltas genéricamente por una función que creará objetos de función de Python:

/// @brief Adapter a member function that returns a unique_ptr to /// a python function object that returns a raw pointer but /// explicitly passes ownership to Python. template <typename T, typename C, typename ...Args> boost::python::object adapt_unique(std::unique_ptr<T> (C::*fn)(Args...)) { return boost::python::make_function( [fn](C& self, Args... args) { return (self.*fn)(args...).release(); }, boost::python::return_value_policy<boost::python::manage_new_object>(), boost::mpl::vector<T*, C&, Args...>() ); }

La lambda delega a la función original y releases() propiedad de la instancia a Python, y la política de llamada indica que Python tomará posesión del valor devuelto por la lambda. El mpl::vector describe la firma de la llamada a Boost.Python, lo que le permite administrar adecuadamente la función de despacho entre los idiomas.

El resultado de adapt_unique se expone como SpamFactory.make() :

boost::python::class_<SpamFactory>(...) .def("make", adapt_unique(&SpamFactory::make)) // ... ;

La adaptación genérica de SpamFactory::consume() es más difícil, pero es bastante fácil escribir una función auxiliar simple:

/// @brief Wrapper function for SpamFactory::consume_spam(). This /// is required because Boost.Python will pass a handle to the /// Spam instance as an auto_ptr that needs to be converted to /// convert to a unique_ptr. void SpamFactory_consume( SpamFactory& self, std::auto_ptr<Spam> ptr) // Note auto_ptr provided by Boost.Python. { return self.consume(std::unique_ptr<Spam>{ptr.release()}); }

La función auxiliar delega a la función original y convierte el auto_ptr proporcionado por Boost.Python al unique_ptr requerido por la API. La función auxiliar SpamFactory_consume se expone como SpamFactory.consume() :

boost::python::class_<SpamFactory>(...) // ... .def("consume", &SpamFactory_consume) ;

Aquí hay un ejemplo de código completo:

#include <iostream> #include <memory> #include <boost/python.hpp> /// @brief Mockup Spam class. struct Spam { Spam(std::size_t x) : x(x) { std::cout << "Spam()" << std::endl; } ~Spam() { std::cout << "~Spam()" << std::endl; } Spam(const Spam&) = delete; Spam& operator=(const Spam&) = delete; std::size_t x; }; /// @brief Mockup factor for Spam. struct SpamFactory { /// @brief Create Spam instances. std::unique_ptr<Spam> make(const std::string& str) { return std::unique_ptr<Spam>{new Spam{str.size()}}; } /// @brief Delete Spam instances. void consume(std::unique_ptr<Spam>) {} }; /// @brief Adapter a non-member function that returns a unique_ptr to /// a python function object that returns a raw pointer but /// explicitly passes ownership to Python. template <typename T, typename ...Args> boost::python::object adapt_unique(std::unique_ptr<T> (*fn)(Args...)) { return boost::python::make_function( [fn](Args... args) { return fn(args...).release(); }, boost::python::return_value_policy<boost::python::manage_new_object>(), boost::mpl::vector<T*, Args...>() ); } /// @brief Adapter a member function that returns a unique_ptr to /// a python function object that returns a raw pointer but /// explicitly passes ownership to Python. template <typename T, typename C, typename ...Args> boost::python::object adapt_unique(std::unique_ptr<T> (C::*fn)(Args...)) { return boost::python::make_function( [fn](C& self, Args... args) { return (self.*fn)(args...).release(); }, boost::python::return_value_policy<boost::python::manage_new_object>(), boost::mpl::vector<T*, C&, Args...>() ); } /// @brief Wrapper function for SpamFactory::consume(). This /// is required because Boost.Python will pass a handle to the /// Spam instance as an auto_ptr that needs to be converted to /// convert to a unique_ptr. void SpamFactory_consume( SpamFactory& self, std::auto_ptr<Spam> ptr) // Note auto_ptr provided by Boost.Python. { return self.consume(std::unique_ptr<Spam>{ptr.release()}); } BOOST_PYTHON_MODULE(example) { namespace python = boost::python; python::class_<Spam, boost::noncopyable>( "Spam", python::init<std::size_t>()) .def_readwrite("x", &Spam::x) ; python::class_<SpamFactory>("SpamFactory", python::init<>()) .def("make", adapt_unique(&SpamFactory::make)) .def("consume", &SpamFactory_consume) ; }

Python interactivo:

>>> import example >>> factory = example.SpamFactory() >>> spam = factory.make("a" * 21) Spam() >>> spam.x 21 >>> spam.x *= 2 >>> spam.x 42 >>> factory.consume(spam) ~Spam() >>> spam.x = 100 Traceback (most recent call last): File "<stdin>", line 1, in <module> Boost.Python.ArgumentError: Python argument types in None.None(Spam, int) did not match C++ signature: None(Spam {lvalue}, unsigned int)


Mi sugerencia es obtener el puntero sin std::unique_ptr contenedor std::unique_ptr con get() . Tendrá que tener cuidado de mantener el unique_ptr de unique_ptr durante todo el tiempo que desee utilizar el valor del puntero sin formato; de lo contrario, el objeto se eliminará y tendrá un puntero a un área de memoria no válida.