【问题标题】:Automatic conversion to boost shared_ptr in boost python在 boost python 中自动转换为 boost shared_ptr
【发布时间】:2014-01-24 12:57:24
【问题描述】:

我注册了一个将二维 NumPy-Array 转换为特征矩阵(称为 DenseMatrix)的转换器:

boost::python::converter::registry::push_back(
        &convertible,
        &construct,
        boost::python::type_id<DenseMatrix>()
);

如果我有一个接受 DenseMatrix 对象并将其导出到 Python 的函数,我可以使用 NumPy-Array 从 Python 调用它。 另一方面,如果我有一个函数接受 boost::shared_ptr (或指针或引用)并将其导出到 Python,我在从 Python 调用它时会收到如下错误:

Boost.Python.ArgumentError: Python argument types in
    libNSM.takeReference(numpy.ndarray)
did not match C++ signature:
    takeReference(Eigen::Matrix<double, -1, -1, 1, -1, -1> {lvalue})

我不想写一个从 NumPy-Array 到 boost::shared_ptr 的显式转换器。有没有更好的方法来解决这个问题?

这里是对应的代码,要清楚一点:

void takeObject(DenseMatrix m) {
    // ...
}
void takePointer(DenseMatrix* ptr) {
    // ...
}
void takeReference(DenseMatrix* ptr) {
    // ...
}
void takeSharedPointer(boost::shared_ptr<DenseMatrix> ptr) {
    // ...
}
BOOST_PYTHON_MODULE(boostModule) {
    def("takeObject", &takeObject);
    def("takePointer", &takePointer);
    def("takeReference", &takeReference);
    def("takeSharedPointer", &takeSharedPointer);
}

【问题讨论】:

    标签: boost-python


    【解决方案1】:

    如果您需要可修改的DenseMatrix 并且复制DenseMatrix 的成本很高,那么拥有将numpy.ndarray 转换为管理DenseMatrix 的可复制构造智能指针的转换器,例如boost::shared_ptr,可能是最好的选择。

    Boost.Python 不支持custom lvalue converters。为了减少悬空引用的机会并在语言之间提供明确的方向性,Boost.Python 将通过 const 引用将转换产生的对象传递给函数。因此,当自定义转换器提供参数时,functions 参数必须通过值或 const 引用接受参数:

    • takeObject() 作为 DenseMatrix 参数将通过复制构造函数构造。
    • takeReference(DenseMatrix&amp;) 失败,因为 DenseMatrix&amp; 无法绑定到 const DenseMatrix&amp;

    如果已经注册了将numpy.ndarray 转换为DenseMatrix 的自定义转换器,那么在将numpy.ndarray 注册到管理DenseMatrix 的智能指针时,可能可以重用大部分代码。

    这是一个完整的示例,显示在两个自定义转换器之间共享的代码,这些转换器将 Python 对象包含一个 int xy 属性到一个 Spam 对象。此外,该示例还展示了如何使用辅助函数通过非常量引用或指针传递转换后的对象。

    #include <iostream>
    #include <boost/make_shared.hpp>
    #include <boost/python.hpp>
    #include <boost/shared_ptr.hpp>
    
    /// @brief Mockup Spam class.
    struct Spam
    {
      int x;
      int y;
      Spam()  { std::cout << "Spam()" << std::endl; }
      ~Spam() { std::cout << "~Spam()" << std::endl; }
    
      Spam(const Spam& rhs) : x(rhs.x), y(rhs.y)
        { std::cout << "Spam(const Spam&)" << std::endl; }
    };
    
    /// @brief Helper function to ceck if an object has an attributed with a
    ///        specific type.
    template <typename T>
    bool hasattr(const boost::python::object& obj,
                 const char* name)
    {
      return PyObject_HasAttrString(obj.ptr(), name) &&
             boost::python::extract<T>(obj.attr(name)).check();
    }
    
    /// @brief Helper type that provides conversions from a Python object to Spam.
    struct spam_from_python
    {
      spam_from_python()
      {
        boost::python::converter::registry::push_back(
          &spam_from_python::convertible,
          &spam_from_python::construct,
          boost::python::type_id<Spam>());
      }
    
      /// @brief Check if PyObject contains an x and y int attribute.
      static void* convertible(PyObject* object)
      {
        namespace python = boost::python;
        python::handle<> handle(python::borrowed(object));
        python::object o(handle);
    
        // If x and y are not int attributes, then return null.
        if (!hasattr<int>(o, "x") && hasattr<int>(o, "y"))
          return NULL;
    
        return object;
      }
    
      /// @brief Convert PyObject to Spam.
      static void construct(
        PyObject* object,
        boost::python::converter::rvalue_from_python_stage1_data* data)
      {
        // Obtain a handle to the memory block that the converter has allocated
        // for the C++ type.
        namespace python = boost::python;
        typedef python::converter::rvalue_from_python_storage<Spam> storage_type;
        void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
    
        // Allocate the C++ type into the converter's memory block, and assign
        // its handle to the converter's convertible variable.
        Spam* spam;
        data->convertible = spam = new (storage) Spam();
    
        // Initialize spam from an object.
        initialize_spam(spam, object);
      }
    
      /// @brief Initialize a spam instance based on a python object.
      static void initialize_spam(Spam* spam, PyObject* object)
      {
        namespace python = boost::python;
        python::handle<> handle(python::borrowed(object));
        python::object o(handle);
    
        spam->x = python::extract<int>(o.attr("x"));
        spam->y = python::extract<int>(o.attr("y"));
      } 
    };
    
    /// @brief Helper type that provides conversions from a Python object to
    ///        boost::shared_ptr<Spam>.
    struct shared_spam_from_python
    {
      shared_spam_from_python()
      {
        boost::python::converter::registry::push_back(
          &spam_from_python::convertible,
          &shared_spam_from_python::construct,
          boost::python::type_id<boost::shared_ptr<Spam> >());
      }
    
      /// @brief Convert PyObject to boost::shared<Spam>.
      static void construct(
        PyObject* object,
        boost::python::converter::rvalue_from_python_stage1_data* data)
      {
        // Obtain a handle to the memory block that the converter has allocated
        // for the C++ type.
        namespace python = boost::python;
        typedef python::converter::rvalue_from_python_storage<
                                            boost::shared_ptr<Spam> > storage_type;
        void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
    
        // Allocate the C++ type into the converter's memory block, and assign
        // its handle to the converter's convertible variable.
        boost::shared_ptr<Spam>* spam;
        data->convertible = spam =
            new (storage) boost::shared_ptr<Spam>(boost::make_shared<Spam>());
    
        // Initialize spam from an object.
        spam_from_python::initialize_spam(spam->get(), object);
      }
    };
    
    /// @brief Mockup functions acceping Spam in different ways.
    void by_value(Spam spam)            { std::cout << "by_value()" << std::endl; }
    void by_const_ref(const Spam& spam) { std::cout << "by_cref()"  << std::endl; }
    void by_ref(Spam& spam)             { std::cout << "by_ref()"   << std::endl; }
    void by_ptr(Spam* spam)             { std::cout << "by_ptr()"   << std::endl; }
    
    /// @brief Use auxiliary functions that accept boost::shared_ptr<Spam> and 
    ///        delegate to functions that have formal parameters of Spam& and
    ///        Spam*.
    void by_ref_wrap(boost::shared_ptr<Spam> spam) { return by_ref(*spam); }
    void by_ptr_wrap(boost::shared_ptr<Spam> spam) { return by_ptr(spam.get()); }
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
    
      // Enable python to Spam conversion.
      spam_from_python();
    
      // Enable python to boost::shared_ptr<Spam> conversion.
      shared_spam_from_python();
    
      // Expose functions that have parameters that can accept a const Spam&
      // argument.
      python::def("by_value",     &by_value);
      python::def("by_const_ref", &by_const_ref);
    
      // Expose functions that have parameters that can accept a const
      // boost::shared_ptr<Spam>& argument.  As copies of shared_ptr are cheap,
      // a copy is used and the managed instance is passed to other functions,
      // allowing Spam& and Spam* parameters.
      python::def("by_ptr", &by_ptr_wrap);
      python::def("by_ref", &by_ref_wrap);
    }
    

    互动使用:

    >>> class Egg:
    ...     x = 1
    ...     y = 2
    ... 
    >>> import example
    >>> example.by_value(Egg())
    Spam()
    Spam(const Spam&)
    by_value()
    ~Spam()
    ~Spam()
    >>> example.by_const_ref(Egg())
    Spam()
    by_cref()
    ~Spam()
    >>> example.by_ref(Egg())
    Spam()
    by_ref()
    ~Spam()
    >>> example.by_ptr(Egg())
    Spam()
    by_ptr()
    ~Spam()
    

    【讨论】:

    • 所以似乎必须为 boost::shared_ptr 提供显式转换器。我真的希望在 boost::python 中有一个隐式转换器。 @Tanner:非常感谢您的详细回答和解释!
    猜你喜欢
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 2017-10-08
    • 2011-05-07
    • 1970-01-01
    相关资源
    最近更新 更多