boost :: python protected destructor issue

时间:2015-11-09 10:34:35

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

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

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

  • 暴露EventListener::on_event(const Event&),因为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()将复制它包装的函数的签名,但它永远不会实际调用包装函数。因此,包装函数可以具有no-op / empty实现,但是如果删除了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 ++时,不会发生向上转换。通过Listener.on_event()调用example.do_on_event()时,event对象的类型为example.Event而不是example.EventB