C++事件系统 - 多态事件和事件处理程序
C++ Event System - Polymorphic Events and Event Handlers
我已经写了我能想到的最基本的事件系统。我来自javascript背景,所以我遵循On
,Off
,Emit
语法。目的是能够创建一个EventSystem
,该可以Emit
任何类型的派生Event
对象并调用适当的处理程序。
请注意,由于原因,我被困在 C++98
到目前为止,我最好的想法是为每个Event
类型提供一个简单的Event
对象和一个 typedef 来处理它。
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 Alarmn";
else std::cerr << "PM Alarmn";
}
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&)
。
如果您的函数采用引用或指针,则可以将多态子类型传递到其中。因此,您只需要一种类型的函数ptr。
typedef void (*EventHandler)(Event& event);
或
typedef void (*EventHandler)(Event* event);
相关文章:
- C++事件系统 - 多态事件和事件处理程序
- wxWidgets 拖放文件事件处理程序初始化问题(无效static_cast)
- 如何将IEnumerable与Process.Start的输出事件处理程序一起使用?
- "动态创建的事件处理程序"复选框
- Wxwidgets - 如何添加调整大小事件处理程序以使"X"绘图随窗口调整大小?
- Qt 键按事件处理程序仅在按下 ctrl、alt 或 shift 键时做出反应
- 类成员作为 C++ 中 C 样式事件的事件处理程序
- C 设计事件处理程序类
- 将托管事件处理程序传递给 Linux 中的非托管代码
- 如何在 c++ linux 中创建事件处理程序
- 解除 lambda 事件处理程序的绑定
- 这是MFC C 中单个选择的ComboBox的事件处理程序
- 指纹读取器 - 从 C# 代码创建事件处理程序到C++的问题
- 为什么我的Windows控制台关闭事件处理程序超时
- 无法让 matplotlib 事件处理程序与 Boost.Python 一起工作
- 事件处理程序是Embarcadero C++Builder中的重入程序吗
- 将事件处理程序添加到C 中的按钮
- 将事件处理程序添加到任何控件都会导致 Visual Studio 中出错
- 尝试实现 Windows API 包装器的事件处理程序时遇到问题
- 如何将事件处理程序添加到桌面窗口