提升::p ython 受保护的析构函数问题

boost::python protected destructor issue

本文关键字:析构函数 问题 ython 提升 受保护      更新时间:2023-10-16
namespace test_py
{
class Event
{
public:
    enum Type { BEGIN = 0, RESULT, END };
    Type get_type( ) const { return m_type; }
protected:
    Event( ) { }
    ~Event( ) { }
    Type m_type;
};
class EventBegin : public Event
{
public:
    EventBegin( ) { m_type = Event::BEGIN; }
    ~EventBegin( ) {}
};
class EventResult : public Event
{
public:
    EventResult( int result ) { m_type = Event::RESULT; m_result = result; }
    ~EventResult( ) {}
    int get_result( ) { return m_result; }
protected:
    int m_result;
};
class EventEnd : public Event
{
public:
    EventEnd( ) { m_type = Event::END; }
    ~EventEnd( ) {}
};
class EventListener
{
public:
    virtual void on_event( const Event& event ) = 0;
};

struct EventListenerWrap: EventListener, py::wrapper< EventListener >
{
    void
    on_event( const Event& event )
    {
        this->get_override( "on_event" )( event );
    }
};
BOOST_PYTHON_MODULE( test_py )
{
    {
        py::scope outer = py::class_< Event, boost::noncopyable >( "Event", py::no_init )
            .add_property( "event_type", &Event::get_type );
        py::enum_< Event::Type >( "EventType" )
            .value( "BEGIN", Event::BEGIN )
            .value( "RESULT", Event::RESULT )
            .value( "END", Event::END )
            .export_values( );
    }
    {
        py::class_< EventBegin, py::bases< Event > >( "EventBegin" );
    }
    {
        py::class_< EventResult, py::bases< Event > >( "EventResult", py::no_init )
            .def( py::init< int >( ( py::arg( "result" ) ) ) )
            .add_property( "result", &EventResult::get_result );
    }
    {
        py::class_< EventEnd, py::bases< Event > >( "EventEnd" );
    }
    {
        py::class_< EventListenerWrap, boost::noncopyable >( "EventListener", py::no_init )
            .def( "on_event", py::pure_virtual( &EventListener::on_event ) );
    }
}
}

我在事件基类中有一个受保护的构造函数和析构函数,无法更改它。在 Python 2.7 中,我需要从 EventListener 类派生并将指针发送回C++代码。在编译过程中,我遇到了这样的错误:

/boost/python/detail/destroy.hpp: In instantiation of ‘static void boost::python::detail::value_destroyer<false>::execute(const volatile T*) [with T = test_py::Event]’:
/boost/python/detail/destroy.hpp:95:36:   required from ‘void boost::python::detail::destroy_referent_impl(void*, T& (*)()) [with T = const test_py::Event]’
/boost/python/detail/destroy.hpp:101:39:   required from ‘void boost::python::detail::destroy_referent(void*, T (*)()) [with T = const test_py::Event&]’
/boost/python/converter/rvalue_from_python_data.hpp:135:71:   required from ‘boost::python::converter::rvalue_from_python_data<T>::~rvalue_from_python_data() [with T = const test_py::Event&]’
/boost/python/converter/arg_from_python.hpp:107:8:   required from ‘PyObject* boost::python::detail::caller_arity<2u>::impl<F, Policies, Sig>::operator()(PyObject*, PyObject*) [with F = void (test_py::EventListener::*)(const test_py::Event&); Policies = boost::python::default_call_policies; Sig = boost::mpl::vector3<void, test_py::EventListener&, const test_py::Event&>; PyObject = _object]’
/boost/python/object/py_function.hpp:38:33:   required from ‘PyObject* boost::python::objects::caller_py_function_impl<Caller>::operator()(PyObject*, PyObject*) [with Caller = boost::python::detail::caller<void (test_py::EventListener::*)(const test_py::Event&), boost::python::default_call_policies, boost::mpl::vector3<void, test_py::EventListener&, const test_py::Event&> >; PyObject = _object]’
EventListener.cpp:193:1:   required from here
EventListener.cpp:18:5: error: ‘test_py::Event::~Event()’ is protected
     ~Event( ) { }
     ^
In file included from /boost/python/converter/rvalue_from_python_data.hpp:10:0,
                 from /boost/python/converter/registry.hpp:9,
                 from /boost/python/converter/registered.hpp:8,
                 from /boost/python/object/make_instance.hpp:10,
                 from /boost/python/object/make_ptr_instance.hpp:8,
                 from /boost/python/to_python_indirect.hpp:11,
                 from /boost/python/converter/arg_to_python.hpp:10,
                 from /boost/python/call.hpp:15,
                 from /boost/python/object_core.hpp:14,
                 from /boost/python/object/class.hpp:9,
                 from /boost/python/class.hpp:13,
                 from ../../defs.hpp:6,
                 from ../defs.hpp:3,
                 from defs.hpp:3,
                 from EventListener.cpp:1:
/boost/python/detail/destroy.hpp:33:9: error: within this context
         p->~T();
         ^
    py::scope outer = py::class_< Event, boost::noncopyable >( "Event", py::no_init )
        .add_property( "event_type", &Event::get_type );

第一眼告诉我你这里有问题。 py::class_<Event, ...>只知道绑定到具有受保护析构函数的Event

您必须Event包装在一个公开析构函数的类中。

如果这是不可能的(例如,因为你不能改变EventBeginEventEnd等的定义(,那么你将不得不编写一个多态容器,通过它自己的内部接口保留派生类,在内部将事件视为非多态对象。

这并不像听起来那么困难:

#include <memory>
namespace test_py
{
    class Event
    {
    public:
        enum Type { BEGIN = 0, RESULT, END };
        Type get_type( ) const { return m_type; }
    protected:
        Event( ) { }
        ~Event( ) { }
        Type m_type;
    };
    class EventBegin : public Event
    {
    public:
        EventBegin( ) { m_type = Event::BEGIN; }
        ~EventBegin( ) {}
    };
    class EventResult : public Event
    {
    public:
        EventResult( int result ) { m_type = Event::RESULT; m_result = result; }
        ~EventResult( ) {}
        int get_result( ) { return m_result; }
    protected:
        int m_result;
    };
    class EventEnd : public Event
    {
    public:
        EventEnd( ) { m_type = Event::END; }
        ~EventEnd( ) {}
    };
    class EventProxy
    {
        // define an interface for turning a non-polymorphic event
        // into a polymorphic one
        struct concept
        {
            virtual const Event* as_event() const = 0;
            virtual ~concept() = default;
        };
        // define a model to support the polymorphic interface for a 
        // non-polymorphic concrete object
        template<class T> struct model : concept
        {
            template<class...Args> model(Args&&... args)
            : _event(std::forward<Args>(args)...)
            {}
            const Event* as_event() const override {
                return &_event;
            }
            T _event;
        };
        // construct the model that contains any Event
        template<class T>
        EventProxy(std::shared_ptr<T> ptr)
        : _impl(std::move(ptr))
        {}
    public:
        // T should be derived from Event...
        template<class T, class...Args>
        static EventProxy create(Args&&... args)
        {
            return EventProxy(std::make_shared<model<T>>(std::forward<Args>(args)...));
        }
        // simply return the address of the internal non-polymorphic event    
        const Event* as_event() const {
            return _impl->as_event();
        }
        // return a shared pointer that points to the internal Event BUT
        // defers lifetime ownership to our internal shared_ptr to 
        // our model. This means we never invoke the polymorphic
        // destructor of Event through the protected interface. 
        std::shared_ptr<const Event> as_shared_event() const {
            return std::shared_ptr<const Event>(_impl, _impl->as_event());
        }
    private:
        // lifetime of the proxy is owned by this shared_ptr.
        std::shared_ptr<concept> _impl;
    };
}
// a quick test.    
auto main() -> int
{
    auto ep = test_py::EventProxy::create<test_py::EventBegin>();
    const test_py::Event* p = ep.as_event();
    std::shared_ptr<const test_py::Event> sp = ep.as_shared_event();
}

当公开一个函数时,Boost.Python 将为每个参数生成转换器。 对于类型为 TT& 的参数,生成的 Python 转换器将保存对象的副本,因此需要访问复制构造函数和析构函数。 此行为的基本原理是防止意外公开悬而未决的引用。 将C++参数传递给 Python 时也是如此。

在以下情况下,此行为会出现问题:

  • 公开EventListener::on_event(const Event&),因为Boost.Python正在尝试创建一个将保存Event副本的对象。 若要解决此问题,请考虑公开一个辅助函数,该函数接受 Event* ,然后委托给原始函数。
  • EventListenerWrap::on_event中将一个Event对象传递给 Python 。 要解决此问题,请考虑将参数包装在 boost::ref()boost::python::ptr() 中。

请注意,通过不创建副本,它为悬而未决的引用创造了机会。 如果实际的Event对象归 Python 所有,那么它的生存期至少需要与 C++ 中对它的任何引用一样长。 同样。 如果实际的Event对象归C++所有,那么它的生存期至少需要与 Python 中对它的任何引用一样长。

struct EventListenerWrap
  : EventListener,
    boost::python::wrapper<EventListener>
{
  void on_event(const Event& event)
  {
    this->get_override("on_event")(boost::ref(event));
  }
};
/// @brief Auxiliary function that will delegate to EventListener::on_event and
///        avoid by-value conversions at the language boundary.  This prevents
///        prevents Boost.Python from creating instance holders that would hold
///        the value as an rvalue.
void event_listener_on_event_aux(EventListener& listener, Event* event)
{
  return listener.on_event(*event);
}
BOOST_PYTHON_MODULE(...)
{
  namespace python = boost::python;
  python::class_<EventListenerWrap, boost::noncopyable>("EventListener")
    .def("on_event", python::pure_virtual(&event_listener_on_event_aux))
    ;
}

一个有趣的细节是,boost::python::pure_virtual()将复制它包装的函数的签名,但它永远不会实际调用包装的函数。 因此,包装的函数可以有一个无操作/空实现,但如果删除pure_virtual指示符或直接调用辅助函数,则提供一个实现是一个好主意。

另外,请注意,要允许 Python 类从 Boost.Python 类派生,Boost.Python 必须公开一个__init__()方法。 不提供任何方法(例如使用 boost::python::no_init() (将导致运行时错误。


下面是一个基于原始代码的最小完整示例,该示例演示了公开具有受保护构造函数和析构函数的类、两个派生类以及通过 Boost.Python 虚拟调度派生类:

#include <iostream>
#include <string>
#include <boost/python.hpp>
// Legacy API.
class event
{
public:
  std::string name;
protected:
  event(std::string name) : name(name) {}
  ~event() = default;
};
struct event_a: event { event_a(): event("event_a") {} };
struct event_b: event { event_b(): event("event_b") {} };
class event_listener
{
public:
  virtual void on_event(const event& event) = 0;
};
// Boost.Python helper types and functions.
struct event_listener_wrap
 : event_listener,
   boost::python::wrapper<event_listener>
{
  void on_event(const event& event)
  {
    std::cout << "event_listener_wrap::on_event()" << std::endl;
    this->get_override("on_event")(boost::ref(event));
  }
};
/// @brief Auxiliary function that will delegate to EventListener::on_event and
///        avoid by-value conversions at the language boundary.  This prevents
///        prevents Boost.Python from creating instance holders that would hold
///        the value as an rvalue.
void event_listener_on_event_wrap(event_listener& listener, event* event)
{
  return listener.on_event(*event);
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose event and suppress default by-value converters and initailizers.
  // This will prevent Boost.Python from trying to access constructors and
  // destructors.
  python::class_<event, boost::noncopyable>("Event", python::no_init)
    .def_readonly("name", &event::name)
    ;
  // Expose event_a and event_b as derived from event.
  python::class_<event_a, python::bases<event>>("EventA");
  python::class_<event_b, python::bases<event>>("EventB");
  // Expose event_listener_wrap.
  python::class_<event_listener_wrap, boost::noncopyable>("EventListener")
    .def("on_event", python::pure_virtual(&event_listener_on_event_wrap))
    ;
  // Expose a function that will perform virtual resolution.
  python::def("do_on_event", &event_listener_on_event_wrap);
}

交互式用法:

>>> import example
>>> class Listener(example.EventListener):
...     def on_event(self, event):
...         assert(isinstance(event, example.Event))
...         print "Listener::on_event: ", event, event.name
... 
>>> listener = Listener()
>>> listener.on_event(example.EventA())
Listener::on_event:  <example.EventA object at 0x7f3bc1176368> event_a
>>> example.do_on_event(listener, example.EventB())
event_listener_wrap::on_event()
Listener::on_event:  <example.Event object at 0x7f3bc1246fa0> event_b

当 Python 直接知道方法时,它将在不通过 Boost.Python 的情况下调用它。 请注意listener.on_event()如何没有通过C++调度,并且event对象保持其example.EventA类型。 另一方面,当调度被迫进入C++时,不会发生向上投射。 当通过example.do_on_event()调用Listener.on_event()时,event对象的类型是example.Event而不是example.EventB