【问题标题】:Virtual override in boost python objects created by static method (or factory (?))由静态方法(或工厂(?))创建的 boost python 对象中的虚拟覆盖
【发布时间】:2025-12-13 17:35:01
【问题描述】:

我正在尝试在 python 中创建一个覆盖 C++ 类中的(纯)虚函数的类(使用boost.python)。问题是 C++ 类是通过静态成员函数创建的(所有构造函数都是私有的或已删除)。我已经成功创建了 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 实现。有谁知道为什么或如何做到这一点?

谢谢!

【问题讨论】:

    标签: c++ python boost factory boost-python


    【解决方案1】:

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

    boost::python::wrapper 层次结构未在从boost::python::make_constructor 返回的函子中初始化。由于wrapper 层次结构没有Python 对象的句柄,get_override() 返回NoneType,并尝试调用NoneType 引发TypeError 异常。

    要解决这个问题,可以显式初始化wrapper 层次结构。下面是一个完整的例子,它提供了一个通用的方法来完成这个。可以使用make_wrapper_constructor(),而不是使用make_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'
    

    【讨论】:

      【解决方案2】:

      我找到了一个似乎可以解决此问题的解决方法。这有点“hacky”的感觉,所以如果有人有更好的解决方案,将不胜感激。

      基本上我写了一个帮助类,所以 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()
      

      它可以工作......但并不像我希望的那样优雅。

      【讨论】: