【问题标题】:boost::python protected destructor issueboost::python 受保护的析构函数问题
【发布时间】:2015-11-09 10:34:34
【问题描述】:
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 ) );
    }
}

}

我在 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();
         ^

【问题讨论】:

  • 我没有过多分析您的代码,但是,您是否有机会将派生类的指针分配给基类指针,并尝试通过基类指针将其删除?

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


【解决方案1】:
    py::scope outer = py::class_< Event, boost::noncopyable >( "Event", py::no_init )
        .add_property( "event_type", &Event::get_type );

第一眼就告诉我你这里有问题。 py::class_&lt;Event, ...&gt; 只知道绑定到具有受保护析构函数的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();
}

【讨论】:

  • 感谢 Richard 的快速回复,但我仍然不清楚如何在 python 绑定中使用新的 EventProxy 类。能否请您将这段代码添加到您的示例中?
  • eventproxy 类为您提供指向事件的指针,您可以在 python 绑定中使用该指针。其目的是将删除器与 Event 接口解耦。
  • 因此您将使用 py::class_ 代替 py::class。您可以根据需要在他的代理上放置尽可能多的方法。它甚至可以复制。
  • 可以通过提供boost::noncopyable 来禁止自动到Python(按值)转换器并提供boost::python::no_init 作为公开可公开访问的构造函数或析构函数的类初始化规范。
【解决方案2】:

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

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

  • 暴露EventListener::on_event(const Event&amp;),因为Boost.Python 正在尝试创建一个对象,该对象将保存Event 的副本。要解决此问题,请考虑公开一个接受 Event* 的辅助函数,然后委托给原始函数。
  • Event 对象传递给EventListenerWrap::on_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(),将导致运行时错误。


这是一个基于原始代码的最小完整示例,demonstrates 公开了一个具有受保护的构造函数和析构函数的类,两个派生类,以及通过 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

【讨论】:

    猜你喜欢
    • 2012-12-07
    • 2011-03-15
    • 2012-11-23
    • 2020-03-06
    • 2011-05-02
    • 2015-10-09
    • 2013-05-18
    • 2014-08-29
    • 2012-02-05
    相关资源
    最近更新 更多