首页 > 解决方案 > C++ 事件系统 - 多态事件和事件处理程序

问题描述

我已经编写了我能想到的最基本的事件系统。我来自 javascript 背景,所以我遵循On, Off,Emit语法。目的是能够创建一个EventSystem可以Emit任何类型的派生Event对象并调用适当的处理程序。

请注意,由于某些原因,我被困在 C++98 中

到目前为止,我最好的想法是Event为每种类型提供一个简单的对象和一个 typedefEvent来处理它。

class Event {};
class AlarmEvent : Event {};
class ErrorEvent : Event {};

typedef void (*EventHandler)(Event event);
typedef void (*AlarmEventHandler)(AlarmEvent event);
typedef void (*ErrorEventHandler)(ErrorEvent event);

我的问题是我希望我的模块能够尽可能容易地附加。

int main()
{
    Module module;

    EventSystem es;
    Event shutdown_event("shutdown");    
    AlarmEvent alarm_event("alarm", "Oh crap");

    es.On("shutdown", module.OnEvent);
    es.On("shutdown", module.OnEvent);
    es.On("alarm", module.OnAlarmEvent);

    es.Emit(shutdown_event);
    es.Emit(alarm_event);
}

但是看着EventSystem

class EventSystem {
    public: 
        void On(std::string id, EventHandler handler);
        void Emit(Event event);
        void GetEventHandlers(std::string id, std::vector<EventHandler> *&handlers);
        std::map<std::string, std::vector<EventHandler> > events;
};

对于每种事件类型,我都需要一个OnGetEventHandlers和属性。events这很快就会变得很糟糕。有没有更好的方法可以让我使用模板EventSystem来保持尽可能简单?

标签: c++

解决方案


C++98 是旧的,比可变参数模板更早。下面模拟带有链表的可变参数模板,这是非常次优的,但它应该可以工作。

// linked lists for "variadic" templates
struct Nil { };
template<typename X, typename XS>
struct Cons { };

// utility type
struct BlackHole {
    template<typename T>
    BlackHole(const T&) { }
};
// anything can be converted to a BlackHole implicitly, but it's a "worse"
// conversion than being converted to a base class

// I would template your event system over every event type
// this implementation only works properly if more derived events appear before their bases
template<typename Events> // e.g. Events = Cons<AlarmEvent, Cons<ErrorEvent, Cons<Event, Nil>>>
class EventSystem;

template<>
class EventSystem<Nil> {
    protected:
    // see below for Emit/EmitEmitted thing
    // usage of BlackHole means that e.g. if calling with AlarmEvent
    // and only overloads for Event and BlackHole are visible
    // then the former will be chosen, since the latter conversion is worse
    // can't just say template<typename T> EmitEmitted(T const&) { }
    void EmitEmitted(BlackHole) { }

    public:
    // these overloads exist so the using declarations ahead don't fail
    // for maximum type-safety, create a private type and
    // make it an argument of each, so they can never be called
    // using Emit/EmitEmitted creates type safety; again, see below
    void Emit() { }
    // On has easy type safety: you just can't call it for an unknown type
    void On() { }
    // GetEventHandlers doesn't really make sense anyway
    // I don't think you need it, you can't have a vector of mixed handlers
    // so why bother?
};
template<typename X, typename XS>
class EventSystem<Cons<X, XS> > : public EventSystem<XS> {
    std::vector<void (*)(X)> handlers;

    protected:
    // "forward" all the EmitEmitted overloads made for XS
    using EventSystem<XS>::EmitEmitted;
    // overload for the specific case of an X
    void EmitEmitted(X x) {
        // fire all of the X-specific handlers
        for(typename std::vector<void (*)(X)>::iterator i = handlers.begin(); i != handlers.end(); ++i) {
            (*i)(x);
        }
        // call the rest of the handlers
        EventSystem<XS>::EmitEmitted(x);
    }

    public:
    // more "forwarding"
    using EventSystem<XS>::Emit;
    void Emit(X x) {
        return EmitEmitted(x);
    }
    // suppose you have an EventSystem<Cons<std::string, Nil> >
    // if you Emit an int, say, then you want this to fail
    // thus the overload of Emit in EventSystem<Nil> should not be
    // a catch-all or anything
    // however, if you emit a std::string, then you need to recursively
    // emit from EventSystem<Nil>, to handle any handlers for superclasses
    // now you don't want it to explode
    // solution? two functions
    // Emit is the public entry point, and fails on unknown types
    // EmitEmitted is named so because, once it's called, the type
    // is known to be known, and will/has been emitted by at least one layer
    // it no-ops once the base case is reached
    // it is protected, and it is where the actual logic is

    // easy now, right?
    using EventSystem<XS>::On;
    void On(void (*handler)(X)) {
        handlers.push_back(handler);
    }
};

示例用法:

struct Event {
    std::string message;
    Event(std::string message) : message(message) { }
};
void HandleEvent(Event e) {
    std::cerr << e.message << "\n";
}
class AlarmEvent : public Event {
    int hour;
    int minute;
    static std::string BuildMessage(int hour, int minute) {
        std::stringstream builder;
        builder << "Alarm: " << std::setfill('0');
        builder << std::setw(2) << hour << ":";
        builder << std::setw(2) << minute;
        return builder.str();
    }
    friend void HandleAlarm(AlarmEvent);

    public:
    AlarmEvent(int hour, int minute) : Event(BuildMessage(hour, minute)), hour(hour), minute(minute) { }
};
void HandleAlarm(AlarmEvent a) {
    // please ignore the fact that this is very stupid
    if((a.hour + (a.minute / 60)) % 24 < 12) std::cerr << "AM Alarm\n";
    else std::cerr << "PM Alarm\n";
}
struct ErrorEvent : Event {
    ErrorEvent(std::string message) : Event(message) { }
};
void HandleError(ErrorEvent) {
    static int count = 1;
    std::cerr << "Error " << count++ << "\n";
}

int main() {
    EventSystem<Cons<AlarmEvent, Cons<ErrorEvent, Cons<Event, Nil> > > > system;
    // all handled by overload resolution
    // no need to say what type you're dealing with
    system.On(HandleEvent);
    system.On(HandleAlarm);
    system.On(HandleError);
    // doesn't work
    // system.On(std::exit)

    system.Emit(ErrorEvent("Bad things"));
    system.Emit(AlarmEvent(2, 30));
    system.Emit(Event("Something happened"));
    system.Emit(ErrorEvent("More bad things"));
    system.Emit(AlarmEvent(11, 67));
    // doesn't work
    // system.Emit(5);
}

不确定所有示例代码都是 C++98,但这没关系。它似乎工作得很好。此外,这里有很多复制。建议将处理程序从void (*)(T)(需要副本)更改为void (*)(T&)void (*)(T const&)


推荐阅读