c++ - 由静态方法(或工厂(?))创建的 boost python 对象中的虚拟覆盖

标签 c++ python boost factory boost-python

我正在尝试在 python 中创建一个类,该类覆盖 C++ 类中的(纯)虚函数(使用 boost.python)。问题是 C++ 类是通过静态成员函数创建的(所有构造函数都是私有(private)的或已删除)。我已经成功创建了 Python“知道”的 Base 类和 BaseWrap 类。我还能够创建一个可以在 python 中重写的纯虚函数。但是,我的问题是当 Base 的成员函数调用纯虚函数时。发生这种情况时,类找不到 python 实现,程序崩溃。

这是 C++ 代码:

#include <iostream>
#include <boost/python.hpp>
#include <boost/static_assert.hpp>

#define CREATE(NAME) \
  static std::shared_ptr<NAME> Create() { \
    std::cout << "STATIC BASE CREATE" << std::endl; \
    return std::make_shared<NAME>();  \
  }

class Base {
protected:
  Base() { std::cout << "BASE DEFAULT CONSTRUCTOR" << std::endl; }
private:

  std::string CallSay() {
    return Say(); 
  }

  virtual std::string Say() const = 0;
};

class BaseWrap : public Base, public boost::python::wrapper<Base> {
public:
  BaseWrap() : Base() { std::cout << "BASEWRAP DEFAULT CONSTRUCTOR" << std::endl; }

  virtual std::string Say() const override
  {
    std::cout << "C++ Say" << std::endl;
    return this->get_override("say") ();
  }

  CREATE(BaseWrap)
};

BOOST_PYTHON_MODULE(Example)
{
  namespace python = boost::python;

  // Expose Base.
  python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
    .def("__init__", python::make_constructor(&BaseWrap::Create))
    .def("Say", python::pure_virtual(&Base::Say))
    .def("CallSay", &Base::CallSay);
}

以及用于测试问题的Python代码:

import sys
import Example

class PythonDerived(Example.Base):
    def __init__(self):
        print "PYTHON DEFAULT CONSTRUCTOR"
        Example.Base.__init__(self)

    def Say(self):
         return "Python Say"

d = PythonDerived()
print d
print 
print d.Say()
print
print d.CallSay()

运行时会给出输出:

PYTHON DEFAULT CONSTRUCTOR
STATIC BASE CREATE
BASE DEFAULT CONSTRUCTOR
BASEWRAP DEFAULT CONSTRUCTOR
<__main__.PythonDerived object at 0x1091caf70>

Python Say

C++ Say
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    print d.CallSay()
 TypeError: 'NoneType' object is not callable

看起来Base::CallSay方法正在查找BaseWrap::Say的实现,但找不到python实现。有谁知道为什么或如何使其工作?

谢谢!

最佳答案

这看起来像是 Boost.Python 中的一个错误。

boost::python::wrapperboost::python::make_constructor 返回的仿函数中层次结构未得到初始化。随着wrapper层次结构没有 Python 对象的句柄,get_override()返回NoneType ,并尝试调用NoneType提出TypeError异常。

要解决此问题,可以显式初始化 wrapper等级制度。下面是一个完整的示例,提供了实现此目的的通用方法。而不是使用 make_constructor() ,可以使用make_wrapper_constructor() 。我选择不使用 C++11 功能。因此,将有一些样板代码可以通过可变参数模板来减少,但移植到 C++11 应该相当简单。

#include <iostream>
#include <boost/function_types/components.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/make_shared.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/python.hpp>

namespace detail {

/// @brief wrapper_constructor will force the initialization
///        of the wrapper hierarchy when a class is held by
///        another type and inherits from boost::python::wrapper.
template <typename Fn>
class wrapper_constructor
{
public:

  typedef typename boost::function_types::result_type<Fn>::type result_type;

public:

  /// @brief Constructor.
  wrapper_constructor(Fn fn)
    : constructor_(boost::python::make_constructor(fn))
  {}

  /// @brief Construct and initialize python object.
  result_type operator()(boost::python::object self)
  {
    constructor_(self);
    return initialize(self);
  }

  /// @brief Construct and initialize python object.
  template <typename A1>
  result_type operator()(boost::python::object self, A1 a1)
  {
    constructor_(self, a1);
    return initialize(self);
  }

  // ... overloads for arguments, or use variadic templates.

private:

  /// @brief Explicitly initialize the wrapper.
  static result_type initialize(boost::python::object self)
  {
    // Extract holder from self.
    result_type ptr = boost::python::extract<result_type>(self);

    // Explicitly initialize the boost::python::wrapper hierarchy.
    initialize_wrapper(self.ptr(),        // PyObject.
                       get_pointer(ptr)); // wrapper hierarchy.

    return ptr;
  }

private:
  boost::python::object constructor_;
};

} // namespace detail

/// @brief Makes a wrapper constructor (constructor that works with
///        classes inheriting from boost::python::wrapper).
template <typename Fn>
boost::python::object make_wrapper_constructor(Fn fn)
{
  // Python constructors take the instance/self argument as the first
  // argument.  Thus, inject the 'self' argument into the provided
  // constructor function type.
  typedef typename boost::function_types::components<Fn>::type
      components_type;
  typedef typename boost::mpl::begin<components_type>::type begin;
  typedef typename boost::mpl::next<begin>::type self_pos;
  typedef typename boost::mpl::insert<
    components_type, self_pos, boost::python::object>::type signature_type;

  // Create a callable python object that defers to the wrapper_constructor.
  return boost::python::make_function(
    detail::wrapper_constructor<Fn>(fn),
    boost::python::default_call_policies(),
    signature_type());
}

class Base
{
protected:
  Base(int x) : x(x) { std::cout << "BASE DEFAULT CONSTRUCTOR" << std::endl; }
  virtual ~Base() {}
  int x;
public:
  std::string CallSay() { return Say(); }
  virtual std::string Say() const = 0;
};

class BaseWrap:
  public Base,
  public boost::python::wrapper<Base>
{
public:
  BaseWrap(int x):
    Base(x)
  { std::cout << "BASEWRAP DEFAULT CONSTRUCTOR" << std::endl; }

  virtual std::string Say() const 
  {
    std::cout << "C++ Say: " << x << std::endl;
    return this->get_override("Say")();
  }

  static boost::shared_ptr<BaseWrap> Create(int x)
  {
    return boost::make_shared<BaseWrap>(x);
  }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose Base.
  python::class_<BaseWrap, boost::shared_ptr<BaseWrap>,
                 boost::noncopyable>("Base", python::no_init)
    .def("__init__", make_wrapper_constructor(&BaseWrap::Create))
    .def("Say", python::pure_virtual(&Base::Say))
    .def("CallSay", &Base::CallSay)
    ;
}

及其用法:

>>> import example
>>> class PythonDerived(example.Base):
...     def __init__(self, x):
...         print "PYTHON DEFAULT CONSTRUCTOR"
...         example.Base.__init__(self, x)
...     def Say(self):
...          return "Python Say"
... 
>>> d = PythonDerived(5)
PYTHON DEFAULT CONSTRUCTOR
BASE DEFAULT CONSTRUCTOR
BASEWRAP DEFAULT CONSTRUCTOR
>>> d
<__main__.PythonDerived object at 0xb7e688ec>
>>> d.Say()
'Python Say'
>>> d.CallSay()
C++ Say: 5
'Python Say'

关于c++ - 由静态方法(或工厂(?))创建的 boost python 对象中的虚拟覆盖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19254715/

相关文章:

c++ - 如何更改适配器容器的基本底层容器类型?

c++ - 为什么这不是 C/C++ 中的无限循环

python - Matplotlib 中 3-D 散点图中的 z 轴缩放和限制

python-pptx 访问类别轴元素 "tickLblSkip"& "tickMarkSkip"

c++ - 在 C++ 中声明为 const 的数据会发生什么

c++ - 大小为 500000 的部分排序数组的快速排序段错误

python - Tensorflow——keras model.save() 引发 NotImplementedError

c++ - 试图摆脱 C++ boost 警告

c++ - std::bind 函数中没有可行的重载 '='

c++ - 使用 karma 为指针 vector 生成输出