2013-12-14 84 views
20

我對boost.python相當陌生,並試圖將函數的返回值公開給python。Boost.Python:如何公開std :: unique_ptr

函數簽名看起來是這樣的:

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

當調用在Python的功能,我得到以下錯誤:

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

在Python我的函數調用如下:

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

我試圖公開std :: unique_ptr,但不能讓它工作.. 有人知道如何正確地暴露指針類嗎? 謝謝!

編輯: 我試過如下:

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

; 

這個例子編譯,但我仍然得到上面提到的錯誤。 另外,我試圖揭露類Message本身

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

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

回答

14

總之,Boost.Python不支持移動語義,因此不支持std::unique_ptr。 Boost.Python的news/change log沒有跡象表明它已被更新爲C++ 11移動語義。此外,這一feature request對於unique_ptr的支持在一年內還沒有被觸及。

不過,Boost.Python支持通過std::auto_ptr將對象的獨佔所有權轉移到Python和從Python轉移。由於unique_ptr本質上是auto_ptr一個更安全的版本,它應該是相當直截了當地使用unique_ptr的API適應使用auto_ptr的API:

  • 當C++所有權轉移到Python,C++函數必須:
  • 當Python所有權轉移給C++,C++函數必須:
    • 經由auto_ptr接受實例。 FAQ提到從C++返回的帶有manage_new_object策略的指針將通過std::auto_ptr進行管理。
    • 具有經由release()

給定一個API /庫不能被改變auto_ptr釋放控制到一個unique_ptr

/// @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()SpamFactory::consume()需要通過被包裹輔助功能。

函數從C++將所有權轉移給Python可以通過將創建的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...>() 
    ); 
} 

拉姆達委託給原有的功能,和實例的Python releases()所有權,和調用策略表明Python將獲得從lambda返回的值的所有權。 mpl::vector描述了對Boost.Python的調用簽名,允許它正確地管理語言之間的函數調度。

adapt_unique結果公開爲SpamFactory.make()

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

一般地適應SpamFactory::consume()是一個比較困難的,但它是很容易寫一個簡單的輔助功能:

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

輔助功能委託給原始函數,並將Boost.Python提供的auto_ptr轉換爲API所需的unique_ptr。所述SpamFactory_consume輔助功能被公開爲SpamFactory.consume()

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

下面是一個完整的代碼示例:

#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:

>>> 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

非常感謝您提供詳細的答案!我會盡快嘗試,但代碼看起來不錯! – schlimpf

+0

那麼定義爲add_property的std :: unique_ptr呢?我是否必須將它封裝在類定義上,我在其中添加實際的屬性或定義to_python_converter會是一個更好的做法? –

3

我的建議是讓從std::unique_ptr容器原始指針與get()。您必須小心地將unique_ptr保留在您希望使用原始指針值的整個時間範圍內,否則該對象將被刪除,並且您將有一個指向無效內存區域的指針。

+0

您可以加入一個小例子嗎? – schlimpf

+1

試試這個'auto_return_value = class_ >,bost :: noncopyable(「Message」,init <>());' 'Message * msg_ptr = return_value.get ();' – Nicole

+0

這個代碼有意義嗎?我應該在哪裏放? – schlimpf

-1

我覺得現在有沒有辦法做你在找什麼...的原因是因爲std::unique_ptr<Message> someFunc(const std::string &str)由值,這意味着返回兩種情況之一:

  1. 返回值是要被複制(但是unique_ptr is not copyable);
  2. 返回值將被移動(現在的問題是boost :: python不支持移動語義)。 (heyy,我正在使用boost 1,53,不知道最新版本);

is someFunc()創建對象嗎?如果是的,我認爲解決的辦法是創建一個包裝,在NO的情況,可以通過參考返回:

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

暴露類:

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

,也是功能:

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

不幸的是我不能改變圖書館裏的任何東西。該函數創建對象(它調用另一個創建對象的函數) – schlimpf

1

升壓支持movable semanticsunique_ptrsince v.1.55。 但在我的項目,我用以前的版本,做了這樣簡單的包裝:

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

創建unique_ptr<HierarchyT>像Python shierarchy並將它傳遞給通過參考接受它的功能。
Python代碼:

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

其中C++函數是float clusterize(unique_ptr<HierarchyT>& hier,...)
然後訪問結果在Python撥打電話hier()獲得來自的unique_ptr被包裝的對象:

output(hier(), nds)