【问题标题】:Boost.Python: How to expose std::unique_ptrBoost.Python:如何公开 std::unique_ptr
【发布时间】:2014-01-02 02:49:31
【问题描述】:

我对 boost.python 还很陌生,并试图将函数的返回值公开给 python。

函数签名如下所示:

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

【问题讨论】:

    标签: python c++ c++11 unique-ptr boost-python


    【解决方案1】:

    我的建议是使用get()std::unique_ptr 容器中获取原始指针。您必须小心地将unique_ptr 保持在您希望使用原始指针值的整个时间范围内,否则该对象将被删除并且您将拥有一个指向无效内存区域的指针。

    【讨论】:

    • 你能添加一个小例子吗?
    • 试试这个auto return_value = class_&lt;std::unique_ptr&lt;Message, std::default_delete&lt;Message&gt;&gt;, bost::noncopyable ("Message", init&lt;&gt;()); Message *msg_ptr = return_value.get();
    【解决方案2】:

    我认为现在没有办法做你正在寻找的东西......原因是因为std::unique_ptr&lt;Message&gt; someFunc(const std::string &amp;str)是按值返回的,这意味着两件事之一:

    1. 返回值将被复制(但unique_ptr is not copyable);
    2. 返回值将被移动(现在的问题是 boost::python 不支持移动语义)。 (嘿嘿,我用的是 boost 1,53,不确定最新版本);

    someFunc() 是在创建对象吗?如果是,我认为解决方案是创建一个包装器,如果是,您可以通过引用返回:

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

    【讨论】:

    • 不幸的是,我无法更改库中的任何内容。该函数正在创建对象(它正在调用另一个创建对象的函数)
    【解决方案3】:

    简而言之,Boost.Python 不支持移动语义,因此不支持std::unique_ptr。 Boost.Python 的news/change log 没有迹象表明它已针对 C++11 移动语义进行了更新。此外,这个feature requestunique_ptr 的支持已经一年多没有动过。

    尽管如此,Boost.Python 支持通过std::auto_ptr 在 Python 之间传输对象的独占所有权。由于unique_ptr 本质上是auto_ptr 的更安全版本,因此将使用unique_ptr 的API 调整为使用auto_ptr 的API 应该相当简单:

    • 当 C++ 将所有权转让给 Python 时,C++ 函数必须:
    • 当 Python 将所有权转让给 C++ 时,C++ 函数必须:
      • 通过auto_ptr 接受实例。 FAQ 提到使用 manage_new_object 策略从 C++ 返回的指针将通过 std::auto_ptr 进行管理。
      • 通过release()auto_ptr 控制权释放到unique_ptr

    给定一个无法更改的 API/库:

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

    lambda 委托给原始函数,releases() 将实例的所有权委托给 Python,调用策略表明 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_ptrSpamFactory_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)
    

    【讨论】:

    • 非常感谢您的详细解答!我会尽快尝试,但代码看起来不错!
    • 那么定义为 add_property 的 std::unique_ptr 呢。我是否必须将它包装在类定义中,在其中添加实际属性或定义 to_python_converter 会是更好的做法?
    • 关于如何将所有权从 Python 传递给 C++ 的最佳解释
    【解决方案4】:

    Boost 支持 movable semanticsunique_ptr since 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&lt;HierarchyT&gt; 创建为 Python shierarchy 并将其传递给通过引用接受它的函数。
    Python代码:

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

    其中 C++ 函数是 float clusterize(unique_ptr&lt;HierarchyT&gt;&amp; hier,...)
    然后要在 Python 中访问结果,请调用 hier() 从 unique_ptr 中获取包装的对象:

    output(hier(), nds)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-17
      • 1970-01-01
      • 1970-01-01
      • 2012-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-27
      相关资源
      最近更新 更多