2

我正在使用 C++ 中的一个事件守护程序,我想使用成员函数回调。基本上,事件队列将收集守护程序持续服务的事件。有一个带有 ID 的基类 Event 结构,所有事件都将从它派生。我希望为每个事件注册的方法在其签名中使用派生事件类型。

struct Event
{
    unsigned int eventId;
};

struct EventA : public Event
{
    unsigned int x;
    unsigned int y;
};

// and struct EventB, EventC (use your imagination...)

const unsigned int EVENT_A = 1;
const unsigned int EVENT_B = 2;
const unsigned int EVENT_C = 3;

class Foo
{
public:
    void handlerMethod_A(const EventA& e);
    void handlerMethod_B(const EventB& e);
};

class Bar
{
public:
    void handlerMethod_C(const EventC& e);
};

然后守护进程将允许这些类使用它们的“this”指针订阅它们的成员函数。

class EventDaemon
{
public:

    void serviceEvents();

    template <class CallbackClass, class EventType>
    void subscribe(
        const unsigned int eventId,
        CallbackClass* classInstancePtr,
        void (CallbackClass::*funcPtr)(EventType));

private:
    Queue<Event*> eventQueue_;
};

所以在这门课之外你可以做类似的事情:

EventDaemon* ed = new EventDaemon();
Foo* foo = new Foo();
Bar* bar = new Bar();

ed->subscribe(EVENT_A, foo, Foo::handlerMethod_A);
ed->subscribe(EVENT_B, foo, Foo::handlerMethod_B);
ed->subscribe(EVENT_C, bar, Bar::handlerMethod_C);

并且 EventDaemon 循环将沿着

void EventDaemon::serviceEvents()
{
    while (true)
    {
        if (eventQueue_.empty())
        {
            // yield to other threads
        }
        else
        {
            // pop an event out of the FIFO queue
            Event e* = eventQueue_.pop();
            // somehow look up the callback info and use it
            classInstancePtr->*funcPtr(reinterpret_cast<?*>(e));
        }
    }
}

所以我的问题是如何按事件 ID 将“this”指针和成员函数指针存储在某种数组中。这样我就可以通过使用 e->eventId 和事件类型来查找 'classInstancePtr' 和 'funcPtr' 以及 reinterpret cast。

4

3 回答 3

3

你工作太辛苦了。使用升压功能:

http://www.boost.org/doc/libs/1_47_0/doc/html/function.html

无论您是否有对象,这些都有效。它们会增加你的编译时间。

请注意,每当您遇到这些类型的问题时,您知道很多人一定有同样的问题,可能有一个简单的选项,如果它不在标准库中,它可能在 boost.js 中。

作为对 Nick 的回应,我不断地将 boost 函数对象放入向量之类的东西中。

我发现,虽然 boost 函数对象可以保存对象引用,但让它们这样做会导致对象生命周期的错误,最好让它们保存类对象的副本(但你会遇到相同的错误,但你尝试持有对您不一定控制其生命周期的对象实例的引用)。图案:

class Foo
{
  struct Member
  {
     // member variable definitions
  };
  shared_ptr<Member> m_; // the only real member variable
public:
  // etc. including the all-important copy
  // constructor and assignment operator and
  // don't forget the member function that gets stuck into
  // the boost function as a callback!
};

所有成员变量都保存在 shared_ptr 中可以提供良好的性能,并且您不必担心函数对象持有的对象的生命周期,因为您可以按值复制它们。线程代码(我现在似乎总是在写)需要额外的东西,比如 Member 中至少有一个 boost mutex 元素或其他方式来确保值不会被踩到。

于 2011-09-09T18:32:11.300 回答
1

boost::function[或者,如果您的系统支持它,std::function] 将this很好地保持指针,如果不需要,则不需要实际对象。所以不是void (SomeType::*)(EventA)你有std::function<void(EventA)>,而是你std::bind酌情打电话。

subscribe(EVENT_A, std::bind(&foo::handleEventA, &foo, std::placeholders::_1));

一个简单的包装函数可用于提供与您最初提议的签名相同的签名并隐藏讨厌的占位符。

当然,您仍然会遇到每个事件类型都有自己的签名的问题,并且需要确保您使用正确的事件 ID 代码。在这两种情况下,您的基本事件类型都可以提供帮助。您的回调不需要接受EventA&; 它可以接受一个Event&,并且dynamic_castEventA在运行时接受一个。ID直接查询类型。

struct Event {
  virtual void ~Event() { }
  virtual int ID() =0;
};

template<typename E>
struct EventHelper : Event {
  virtual int ID() { return E::EventID; }
};

struct EventA : EventHelper<EventA> {
    static const int EventID = 89;
};

现在,如果您有一个Event*对象 [当您发送事件时],您可以p->ID()获取适当的 ID,如果您有一个EventA类型 [当您注册回调时],您可以执行EventA::EventID.

所以现在,无论您拥有什么实际类型的事件,您所需要存储的只是每个回调std::function<void(const Event&)>的一个关联int值。

void subscribe(int id, std::function<void(const Event&)> f) {
    callbacks.insert(std::make_pair(id, f));
}

template<typename E>
void subscribe(std::function<void(const Event&)> f) {
   subscribe(E::EventID, f);
}

template<typename O, typename E>
void subscribe(O* p, void (O::*f)(const Event&)) {
   subscribe<E>(std::bind(f, p, std::placeholders::_1));
}

您仍然存在订阅时用户错误可能导致函数被错误调用的问题。如果您dynamic_cast在回调中正确使用,这将在运行时被捕获,但编译时检查会很好。那么如果我们自动化dynamic_cast呢?对于这一步,我将使用 c++11 lambdas,但它也可以使用多种方法在 C++03 中实现。

template <class CallbackClass, class EventType>
void subscribe(CallbackClass* classInstancePtr, void (CallbackClass::*funcPtr)(EventType)) {
    subscribe<EventType::EventID>([&](const Event& e) { 
       (classInstancePtr->*funcPtr)(dynamic_cast<const EventType&>(e));
    });
}

所以现在我们已经回到原来的界面,你的回调接受他们将要处理的实际类型,但在内部你已经将它们全部压缩到一个公共签名中。

于 2011-09-09T22:39:13.517 回答
0

好的,所以我完成了我最初想要的接口的实现。我正在查看丹尼斯的答案,但最终找到了仿函数,我意识到我正在寻找的是一个简单的多态解决方案。在此之前我未能掌握我可以创建一个非模板化基类,用于将模板化类存储在向量/数组中。我想这就是 mheyman 试图告诉我的……所以我很抱歉我没有马上明白。只是为了澄清一下,尽管我真的在为自己的利益和知识寻找实施解决方案,而不仅仅是为了完成工作的第 3 方库。所以我想我会寻找Boost 函数是如何工作的,而不仅仅是它们存在并且很棒。

如果有人仍然对这里感兴趣,那么我最终得到的重要部分(减去一些无关的东西和错误检查):

  • EventFunctor 基本上是一个指向成员函数模板类的指针
  • EventFunctorBase 是用于将它们存储在向量中的非模板基类
  • 事件在用于调用回调之前使用模板化类型进行动态转换

class EventDaemon
{
public:

    template <class CallbackClass, class EventType>
    void subscribe(
        const EventId eventId,
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

private:
    EventFunctorBase* callbacks_[MAX_NUM_EVENTS];
};

template <class CallbackClass, class EventType>
void EventDaemon::subscribe(
    const EventId eventId,
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
{
    callbacks_[eventId] = new EventFunctor<CallbackClass,EventType>(callbackClassInstancePtr,funcPtr);
}

class EventFunctorBase
{
public:
    EventFunctorBase();
    virtual ~EventFunctorBase();
    virtual void operator()(const Event& e)=0;
};

template <class CallbackClass, class EventType>
class EventFunctor : public EventFunctorBase
{
public:

    EventFunctor(
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

    virtual void operator()(const Event& e);

private:
    CallbackClass* callbackClassInstancePtr_;
    void (CallbackClass::*funcPtr_)(const EventType&);
};

template <class CallbackClass, class EventType>
EventFunctor<CallbackClass,EventType>::EventFunctor(
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
    :
    callbackClassInstancePtr_(callbackClassInstancePtr),
    funcPtr_(funcPtr)
{
}

template <class CallbackClass, class EventType>
/*virtual*/ void EventFunctor<CallbackClass,EventType>::operator()(const Event& e)
{
    (callbackClassInstancePtr_->*funcPtr_)(dynamic_cast<const EventType&>(e));
}

事件守护程序循环

while (true_)
{
    if (eventQueue_->empty())
    {
        // yield to other threads
    }
    else
    {
        Event* e = eventQueue_.pop();
        (*(callbacks_[e->ID]))(*e);
    }
}

我在这里的最后一步是尝试消除让开发人员为每个事件定义一个 ID 的需要……当然,这可能会在本周晚些时候发布一个新帖子。

于 2011-09-13T01:31:21.213 回答