2013-12-14 18 views
20

Sono abbastanza nuovo su boost.python e sto cercando di esporre il valore restituito da una funzione a python.Boost.Python: Come esporre std :: unique_ptr

La firma funzione è simile a questo:

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

Quando si chiama la funzione in python, ottengo il seguente errore:

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

La mia chiamata di funzione in Python è simile al seguente:

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

Ho provato ad esporre lo std :: unique_ptr ma non riesco a farlo funzionare .. Qualcuno sa come esporre correttamente la classe pointer? Grazie!

Edit: ho provato la seguente:

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

; 

Questo esempio compila, ma ancora ottengo l'errore di cui sopra. Inoltre, ho cercato di esporre la classe Message

class_<Message>("Message", init<unsigned>()) 

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

risposta

14

In breve, Boost.Python non supporta la semantica del movimento e, pertanto, non supporta std::unique_ptr. Boost.Python's news/change log non ha alcuna indicazione che sia stato aggiornato per la semantica del movimento C++ 11. Inoltre, questo supporto feature request per il supporto unique_ptr non è stato toccato per oltre un anno.

Tuttavia, Boost.Python supporta il trasferimento della proprietà esclusiva di un oggetto verso e da Python tramite std::auto_ptr.Come unique_ptr è essenzialmente una versione più sicura di auto_ptr, dovrebbe essere abbastanza semplice per adattare un'API utilizzando unique_ptr a un'API che utilizza auto_ptr:

  • Quando storni C++ proprietà al pitone, la funzione C++ deve:
  • Quando Python trasferisce la proprietà a C++, la funzione C++ deve:
    • accettare l'esempio via auto_ptr. Lo FAQ menziona che i puntatori restituiti da C++ con una politica manage_new_object verranno gestiti tramite std::auto_ptr.
    • hanno auto_ptr controllo dell'emissione di un unique_ptr tramite release()

Dato un API/libreria che non può essere modificato:

/// @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>); 
}; 

Il SpamFactory::make() e SpamFactory::consume() Devono essere avvolte tramite funzioni ausiliarie.

Funzioni trasferimento proprietà da C++ per Python possono essere genericamente avvolti da una funzione che crea Python oggetti funzione:

/// @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...>() 
    ); 
} 

I delegati lambda alla funzione originale, e releases() la proprietà dell'istanza a Python, e la politica della chiamata indica che Python assumerà la proprietà del valore restituito dalla lambda. Lo mpl::vector descrive la firma della chiamata su Boost.Python, permettendogli di gestire correttamente l'invio delle funzioni tra le lingue.

Il risultato di adapt_unique è esposto come SpamFactory.make():

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

Genericamente adattando SpamFactory::consume() è più difficile, ma è abbastanza facile da scrivere una semplice funzione ausiliaria:

/// @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 funzione ausiliaria delega alla funzione originale e converte lo auto_ptr fornito da Boost.Python allo unique_ptr richiesto dall'API.La funzione ausiliaria SpamFactory_consume è esposto come SpamFactory.consume():

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

Ecco un esempio di codice 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) 
    ; 
} 

interattivo Python:

>>> 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) 
+0

Grazie mille per la risposta dettagliata! Ci proverò al più presto, ma il codice sembra buono! – schlimpf

+0

E che dire di std :: unique_ptr che è definito come add_property. Devo avvolgerlo sulla definizione della classe, dove sto aggiungendo effettivamente la proprietà o definendo to_python_converter sarebbe una pratica migliore? –

3

Il mio suggerimento è quello di ottenere il puntatore grezzo dal contenitore std::unique_ptr con get(). Dovrai fare attenzione a mantenere lo unique_ptr in scope per tutto il tempo che desideri utilizzare il valore del puntatore raw, altrimenti l'oggetto verrà cancellato e avrai un puntatore a un'area di memoria non valida.

+0

si potrebbe aggiungere un piccolo esempio per favore? – schlimpf

+1

Prova questo 'auto return_value = class_ >, bost :: noncopyable (" Message ", init <>());' 'Messaggio * msg_ptr = return_value.get(); ' – Nicole

+0

questo codice ha senso? dove dovrei metterlo? – schlimpf

-1

Penso che al giorno d'oggi non c'è modo di fare quello che state cercando ... Il motivo è perché std::unique_ptr<Message> someFunc(const std::string &str) sta tornando per valore, il che significa che una delle due cose:

  1. Il valore restituito sta per essere copiati (ma unique_ptr is not copyable);
  2. Il valore restituito verrà spostato (ora il problema è che boost :: python non fornisce supporto per spostare la semantica). (heyy, sto usando boost 1,53, non sono sicuro nelle versioni più recenti);

someFunc() crea l'oggetto? Nel caso in cui sì, penso che la soluzione è quella di creare un involucro, in caso NO, è possibile restituire per riferimento:

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

esporre la classe:

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>()) 
; 

e anche le funzioni:

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

sfortunatamente non posso cambiare nulla nella libreria. La funzione sta creando l'oggetto (chiama un'altra funzione che crea l'oggetto) – schlimpf

1

Boost supporta movable semantics e unique_ptrsince v.1.55. Ma nel mio progetto ho usato versione precedente e ha fatto come semplice involucro:

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") 
    ; 

per creare unique_ptr<HierarchyT> come Python shierarchy e passarlo alla funzione che accetta per riferimento.
codice Python:

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

cui funzione C++ è float clusterize(unique_ptr<HierarchyT>& hier,...).
Poi, per accedere ai risultati in Python effettuare una chiamata hier() per ottenere l'oggetto avvolto dal unique_ptr:

output(hier(), nds) 
Problemi correlati