【问题标题】:Event Callback Daemon事件回调守护进程
【发布时间】:2011-09-09 18:18:07
【问题描述】:

我正在开发一个 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。

【问题讨论】:

    标签: c++ events templates daemon member-function-pointers


    【解决方案1】:

    你工作太辛苦了。使用 boost 函数:

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

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

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

    作为对 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 元素或其他方式来确保值不会被踩到。

    【讨论】:

    • 看起来 boost 函数也被模板化了,我认为这在尝试基于任意订阅存储它们的数组时没有帮助。我对成员函数指针和使用它们有很好的理解,只是不是在这种试图允许从 Event 派生的任何参数的上下文中。
    • 我明白你的意思了,我想...我发布了我的解决方案,使用的方法与 Boost 函数的工作方式相似。
    【解决方案2】:

    boost::function [或者,如果您的系统支持,std::function] 将很好地保存this 指针,如果不需要,则不需要实际对象。所以你有std::function&lt;void(EventA)&gt;,而不是void (SomeType::*)(EventA),你可以适当地调用std::bind

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

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

    当然,您仍然会遇到每个事件类型都有自己的签名的问题,并且需要确保您使用正确的事件 ID 代码。在这两种情况下,您的基本事件类型都可以提供帮助。您的回调不需要接受EventA&amp;;它可以在运行时接受Event&amp;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-&gt;ID() 来获取适当的ID,如果你有一个EventA 类型[当你注册你的回调] 你可以做EventA::EventID

    所以现在,无论您的实际事件类型是什么,您只需为每个回调存储一个 std::function&lt;void(const Event&amp;)&gt; 和一个关联的 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 lambda,但它也可以在 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));
        });
    }
    

    所以现在我们已经回到原来的界面了

    【讨论】:

    • 谢谢!这看起来正是我正在寻找的......我会在周末回家后尝试实施。看来我会在 Lambda Expressions 上阅读,因为这是我第一次听说他们。
    【解决方案3】:

    好的,所以我完成了我最初想要的接口的实现。我正在查看丹尼斯的答案,但最终得到了函子,我意识到我正在寻找的是一个简单的多态解决方案。在此之前我未能掌握我可以创建一个非模板化基类,用于将模板化类存储在向量/数组中。我想这就是 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));
    }
    

    EventDaemon 循环

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

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

    【讨论】:

      猜你喜欢
      • 2023-03-02
      • 1970-01-01
      • 1970-01-01
      • 2014-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-06
      • 2011-02-27
      相关资源
      最近更新 更多