c++ - 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;
};
对于每种事件类型,我都需要一个On
、GetEventHandlers
和属性。events
这很快就会变得很糟糕。有没有更好的方法可以让我使用模板EventSystem
来保持尽可能简单?
解决方案
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&)
。
推荐阅读
- java - 来自 RGB byte[] 数组的 BufferedImage
- r - R:在 Shiny 中使用公式对象来检索描述性统计数据
- javascript - 嵌套的 .then 的 JS 返回值
- c - 与哨点值相反
- c++ - c++ (sys/socket.h) 客户端重试连接到服务器导致 seg 错误
- wordpress - Wordpress 中的 Google Places 自动完成仅澳大利亚限制
- ios - Apple TV - 如何在 UISegmentedControl 中实现粘性焦点选择?
- c# - 访问具有多个泛型类型的类中的静态变量
- c - 如何按字母顺序排列字符串并返回指向已排序字符数组的指针?
- python - Python - 在二进制文件中写入特定数量的字节