事件回调守护进程

发布于 2024-12-03 23:35:15 字数 1886 浏览 4 评论 0原文

我正在 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”。

I am working on an event daemon in C++ that I would like to use member function callbacks. Basically an event queue would collect events which the daemon continuously services. There is a base class Event struct with an ID and all events would derive from it. I would like the methods registered for each event to use the derived event type in their signature.

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

Then the Daemon would allow these classes to subscribe their member functions using their 'this' pointer.

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_;
};

So outside this class you could do something like:

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

And the EventDaemon loop would be along the lines of

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

So my question is how I can store the 'this' pointers and member function pointers in some sort of array by event ID. That way I could look up the 'classInstancePtr' and 'funcPtr' by using e->eventId and the event type as well for the reinterpret cast.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

爱格式化 2024-12-10 23:35:16

你工作太辛苦了。使用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中,可以实现良好的性能,并且您不必担心函数对象保存的对象的生命周期,因为您可以按值复制它们。线程代码(我现在似乎总是在写)需要额外的东西,例如成员中至少一个增强互斥元素或其他某种方式来确保值不会被踩踏。

You are working too hard. Use boost functions:

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

These work whether you have a object or not. They will increase your compile time.

Note, whenever you come across these types of questions where you know many people must have had the same problem, there is probably a simple option and, if it is not in the standard library, it is probably in boost.

In response to Nick, I'm constantly throwing boost function objects into vectors and whatnot.

I've found that, while boost function objects can hold object references, having them do so can lead to bugs with object lifetimes and it is better to have them hold copies of the class objects (you run into the same bugs however you try to hold a reference to a object instance that you don't necessarily control the lifetime of). The pattern:

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!
};

where all the member variables get held in a shared_ptr allows for good performance and you don't have to worry about lifetimes of objects held by function objects because you can copy them by value. Threaded code (what I always seem to be writing nowadays) needs additional things like at least one boost mutex element in Member or some other way to assure values don't get stomped on.

因为看清所以看轻 2024-12-10 23:35:16

boost::function [或者,如果您的系统支持它,std::function] 将很好地保存 this 指针,如果不需要的话,还有一个额外的好处,那就是不需要实际的对象。因此,您使用的是 std::function,而不是 void (SomeType::*)(EventA),并且调用 std::根据需要进行绑定。

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

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

当然,您仍然面临每个事件类型都有自己的签名的问题,并且需要确保使用正确的事件 ID 代码。在这两种情况下,您的基本事件类型都可以提供帮助。您的回调不需要接受 EventA&;它可以接受 Event&,并在运行时将其 dynamic_cast 转换为 EventA。对于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 以及每个回调的关联 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));
    });
}

因此,现在我们已经回到了原始界面,您的回调接受它们将要处理的实际类型,但在内部您已将它们全部压缩到一个共同的签名中。

boost::function [or, if your system supports it, std::function] will take care of holding the this pointer quite well, with the added benefit of not requiring an actual object if it isn't necessary. So instead of void (SomeType::*)(EventA) you have std::function<void(EventA)>, and you call std::bind as appropriate.

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

A trivial wrapper function can be used to provide the same signature as you originally proposed and hide the nasty placeholders.

You do, of course, still have the issue of each event type having its own signature, and the need to ensure you use the correct Event ID code. In both cases, your base Event type can help out. Your callback need not accept an EventA&; it can accept an Event&, and dynamic_cast it to an EventA at runtime. For the ID, query the type directly.

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

Now, if you have an Event* object [when you go to dispatch your events], you can do p->ID() to get the appropriate ID, and if you have a EventA type [when you register your callbacks] you can do EventA::EventID.

So now, all you have to store is a std::function<void(const Event&)> and an associated int value for each of your callbacks, no matter what the actual type of event you have.

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

You still have the issue that user error when subscribing can result in a function being called incorrectly. If you've used dynamic_cast correctly within the callback, this will get caught at runtime, but a compile time check would be nice. So what if we automate that dynamic_cast? For this step, I'm going to use c++11 lambdas, but it can be implemented in C++03 as well using a variety of methods.

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

So now we've gone full circle back to your original interface where your callbacks accept the actual type they are going to be working on, but internally you've squeezed them all into a common signature.

巴黎夜雨 2024-12-10 23:35:16

好的,我完成了我原来想要的接口的实现。我正在查看丹尼斯的答案,但最终找到了函子,我意识到我正在寻找的是一个简单的多态解决方案。在此之前,我无法理解我可以创建一个非模板化基类,用于在向量/数组中存储模板化类。我想这就是 mheyman 想要告诉我的……所以我很抱歉我没有立即明白。只是为了澄清一下,尽管我实际上是为了自己的利益和知识而寻找实施解决方案,而不仅仅是第三方库来完成工作。所以我想我会寻找 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 的需要......当然,这可能会结束稍后会有新帖子 星期。

Okay, so I finished an implementation of my original desired interface. I was looking through Dennis' answer but eventually got lead to functors and I realized what I was looking for was a simple polymorphic solution. I failed to grasp before that I could create a non-templated base class with which to use for storing templated classes in vectors/arrays. I think this is what mheyman was trying to tell me... so I apologize I didn't get it right away. Just to clarify though I was really looking for the implementation solution for my own benefit and knowledge, not just a 3rd party library to get the job done. So I guess I would be looking for how Boost functions work, not just that they exist and are awesome.

If anyone is still interested here are the important parts of what I ended up with (minus some extraneous stuff and error checking):

  • EventFunctor is basically a pointer to member function template class
  • EventFunctorBase is the non-templated base class used to store them in a vector
  • The Event is dynamic cast using the templated type before being used to invoke the callback

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 loop

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

My final steps here will be to try and remove the need to have the developer define an ID for each event... of course this might end up a new post later this week.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文