c++ boostpython对象中的虚重写由静态方法(或工厂(?))创建

sy5wg1nm  于 2023-04-13  发布在  Python
关注(0)|答案(3)|浏览(81)

我试图在python中创建一个类,它覆盖C类中的(纯)虚函数(使用boost.python)。问题是C类是通过静态成员函数创建的(所有的构造函数都是私有的或被删除的)。我已经成功地创建了Base类和一个Python“知道”的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实现。有人知道为什么或如何使其工作吗?
谢谢!

mo49yndu

mo49yndu1#

这看起来好像是Boost.Python中的一个bug。
boost::python::wrapper层次结构没有在从boost::python::make_constructor返回的函子中初始化。由于wrapper层次结构没有Python对象的句柄,get_override()返回NoneType,并且尝试调用NoneType会引发TypeError异常。
要解决这个问题,可以显式地初始化wrapper层次结构。下面是一个完整的示例,提供了一种通用的方法来实现这一点。可以使用make_wrapper_constructor()而不是使用make_constructor()。我选择不使用C11特性。因此,将有一些样板代码可以使用可变模板来减少,但移植到C11应该是相当简单的。

#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'
jdgnovmf

jdgnovmf2#

我找到了一个工作,似乎解决了这个问题。这是一个有点“黑客”的感觉,所以如果任何人有一个更好的解决方案,将不胜感激。
基本上我写了一个helper类,所以C++代码变成了:

#include <iostream>
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/manage_new_object.hpp>
#include <boost/python/return_value_policy.hpp>

#define CREATE(NAME)                                \
  static inline 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;
  }

public:

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

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

class BaseHelper {
public:

  BaseHelper() {}

  virtual std::string eval() = 0;
};

class BaseHelperWrap : public BaseHelper, public boost::python::wrapper<BaseHelper> {
public:

  BaseHelperWrap() : BaseHelper() {}

  virtual std::string eval() override
  {
    return this->get_override("eval") ();
  }
};

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 func->eval();
  }

  CREATE(BaseWrap) 

  static std::shared_ptr<BaseWrap> PyCreate(std::shared_ptr<BaseHelper> const& f)
  {
    std::shared_ptr<BaseWrap> ptr = Create();
    ptr->set_func(f);
    return ptr;
  }

private:

  void set_func(std::shared_ptr<BaseHelper> const& f)
  {
    func = f;
  }

  std::shared_ptr<BaseHelper> func;
};

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

  python::def("make_foo", make_foo, python::return_value_policy<python::manage_new_object>());

  // Expose Base.
  python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
  .def("Create", &BaseWrap::PyCreate).staticmethod("Create") 
  .def("CallSay", &Base::CallSay);

  python::class_<BaseHelperWrap, std::shared_ptr<BaseHelperWrap>, boost::noncopyable>("BaseHelper", python::init<>())
    .def("eval", python::pure_virtual(&BaseHelper::eval));

  python::implicitly_convertible<std::shared_ptr<BaseHelperWrap>, std::shared_ptr<BaseHelper> >();
}

Python代码:

import sys
import Example

class PyBaseHelper(Example.BaseHelper):
    def eval(self):
        return "Python Say"

h = PyBaseHelper()
d = Example.Base.Create(h)

print 
print
print d.CallSay()

它的工作...但不是作为一个优雅的修复,因为我希望。

hiz5n14c

hiz5n14c3#

我意识到这是一个几乎十年前的问题,但如果有人正在通过使用自定义策略寻找一个优雅的解决方案,我将在这里讨论这个问题。

template<typename HeldType, typename BasePolicies = default_call_policies, int iSelf = -1>
struct initialize_wrapper_policies : BasePolicies
{
    template<typename ArgumentPackage>
    static PyObject *postcall(const ArgumentPackage &args, PyObject *pResult)
    {
        PyObject *pSelf = boost::python::detail::get(boost::mpl::int_<iSelf>(), args);
        boost::python::detail::initialize_wrapper(
            pSelf,
            get_pointer((HeldType)extract<HeldType>(pSelf))
        );

        return BasePolicies::postcall(args, pResult);
    }
};
python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
    .def("__init__", 
        python::make_constructor(
            &BaseWrap::Create,
            initialize_wrapper_policies<std::shared_ptr<BaseWrap> >()
        )
    );

相关问题