轻量级事件派发器核心是用enum class EventType作键、std::unordered_map<EventType, std::vector<std::function<void(const Event&)>>>存储回调,Event含type和data(如std::any),注册/触发对应push_back/遍历调用,生命周期由shared_ptr或显式unsubscribe管理。

事件派发器的核心结构怎么设计
一个轻量级事件派发器不需要模板元编程或反射,关键在于用 std::function 存储回调、用 std::map 或 std::unordered_map 按事件类型索引。事件类型通常用枚举或字符串作 key,推荐用枚举——编译期检查强、无哈希开销、避免拼写错误。
注意:不要把事件数据塞进派发器内部;它只负责“通知”,数据应由调用方传入回调。否则会耦合严重、难以测试。
- 用
enum class EventType定义事件,比如EventType::MouseClick - 存储结构选
std::unordered_map<eventtype std::vector event>>></eventtype> - 每个事件类型可绑定多个监听器,顺序按注册先后(不保证线程安全)
如何注册和触发事件
注册就是往对应事件类型的 vector 里 push_back 一个 std::function;触发就是遍历该 vector 并调用。难点在生命周期管理——如果监听器是临时对象的成员函数,而对象提前析构,再触发就会 crash。
示例注册:dispatcher.subscribe(EventType::KeyDown, [this](const Event& e) { handleKey(e); });
这里捕获 this 是常见写法,但必须确保 this 生命周期长于派发器。
- 避免裸指针或 this 捕获,优先用
std::shared_ptr管理监听器所属对象 - 提供
unsubscribe()接口,返回 token(如 size_t id)便于移除特定监听器 - 触发时建议加 guard:若 vector 为空则直接 return,避免无谓循环
为什么不能直接用 std::function 存回调
因为事件携带数据。如果只存 std::function<void></void>,你就得把事件数据存在全局或单例里,下一次触发前覆盖旧值——多事件并发时必然出错。必须让回调能接收参数。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 正确签名是
std::function<void event></void>,其中Event是轻量结构体,含type和data(可用std::any或 variant) - 若追求零拷贝,
Event的data字段可改为std::shared_ptr<const void></const>,但增加内存分配开销 - 不要为每种事件定义单独回调类型(如
void(int),void(std::string)),那会让注册接口爆炸式增长
线程安全问题怎么处理
默认不加锁。90% 的桌面/游戏应用事件都在主线程派发,强行加 mutex 反而拖慢性能。真需要跨线程(比如网络线程触发 UI 事件),应在上层做队列缓冲,而不是在派发器内部锁整个 map。
- 若必须支持多线程注册/触发,只对单个事件类型的 vector 加锁(而非整个 map),粒度更细
- 避免在回调里调用
subscribe/unsubscribe——可能引发迭代器失效,应改用延迟删除标记 - 调试时可加
#ifdef DEBUG检查当前线程 ID,快速暴露误用场景
最易被忽略的是监听器移除时机:不是所有场景都需要显式取消订阅,但对象析构前没清理,下次事件来就崩。这个责任不在派发器,而在使用者对生命周期的理解。

















