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:
- El valor de retorno se copiará (pero unique_ptr no se puede copiar );
- 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:
- estar expuesto con CallPolicy de
boost::python::return_value_policy
con un convertidor de resultadosboost::python::manage_new_object
. - tiene el control de liberación
unique_ptr
medianterelease()
y devuelve un puntero en bruto
- estar expuesto con CallPolicy de
- 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íticamanage_new_object
se administrarán a través destd::auto_ptr
. - tener
auto_ptr
liberar el control a ununique_ptr
víarelease()
- aceptar la instancia a través de
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.