如何解决C++事件系统中不同GameEvent派生类的std::function存储错误?
修复代码(基于原有架构)
1. 修正事件类的name初始化
首先确保每个派生类事件的name唯一,否则哈希表无法区分不同事件:
#pragma once #include <string> struct GameEvent { std::string name = "Game Event"; }; struct StartEvent : GameEvent { StartEvent() { name = "StartEvent"; } // 初始化唯一名称 std::string msg = "This is the start event!"; }; struct EndEvent : GameEvent { EndEvent() { name = "EndEvent"; } // 初始化唯一名称 std::string msg = "This is the end event"; }; struct Events { static const StartEvent s_StartEvent; static const EndEvent s_EndEvent; }; const StartEvent Events::s_StartEvent; const EndEvent Events::s_EndEvent; struct GameEventHash { std::size_t operator()(const GameEvent& evt) const { return std::hash<std::string>()(evt.name) << 1; } }; struct GameEventEqual { bool operator()(const GameEvent& t1, const GameEvent& t2) const { return t1.name == t2.name; } };
2. 修改事件管理器,修复类型转换与切片问题
#pragma once #include "events.h" #include <functional> #include <unordered_map> #include <vector> #include <type_traits> struct EventManager { template<typename T> static void AddListener(std::function<void(const T&)> listener); static void Broadcast(const GameEvent& evt); private: // 修改function参数为const引用,避免切片 static std::unordered_map<GameEvent, std::vector<std::function<void(const GameEvent&)>>, GameEventHash, GameEventEqual> s_Events; }; inline void EventManager::Broadcast(const GameEvent& evt) { // 先查找是否存在事件,避免[]自动创建空条目 auto iter = s_Events.find(evt); if (iter != s_Events.end()) { for (const auto& listener : iter->second) { listener(evt); } } } template<typename T> void EventManager::AddListener(std::function<void(const T&)> listener) { // 静态断言,确保T是GameEvent的派生类 static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent"); T evt; // 包装listener,将GameEvent转换为T类型后调用 auto wrappedListener = [listener](const GameEvent& baseEvt) { // 因为我们是按T类型注册的监听器,广播时传入的必然是T类型,所以static_cast安全 listener(static_cast<const T&>(baseEvt)); }; // 直接获取对应事件的监听器列表,不存在则自动创建 auto& listeners = s_Events[evt]; listeners.push_back(std::move(wrappedListener)); } std::unordered_map<GameEvent, std::vector<std::function<void(const GameEvent&)>>, GameEventHash, GameEventEqual> EventManager::s_Events;
替代实现思路(更高效可靠)
基于类型索引替代事件对象作为哈希表键,无需维护name字段,彻底避免因名称重复或对象切片导致的问题:
#pragma once #include "events.h" #include <functional> #include <unordered_map> #include <vector> #include <type_traits> #include <typeindex> struct EventManager { template<typename T> static void AddListener(std::function<void(const T&)> listener); template<typename T> static void Broadcast(const T& evt); private: // 用类型索引作为键,直接关联事件类型与监听器 static std::unordered_map<std::type_index, std::vector<std::function<void(const GameEvent&)>>> s_Events; }; template<typename T> void EventManager::Broadcast(const T& evt) { static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent"); auto iter = s_Events.find(std::type_index(typeid(T))); if (iter != s_Events.end()) { for (const auto& listener : iter->second) { listener(evt); } } } template<typename T> void EventManager::AddListener(std::function<void(const T&)> listener) { static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent"); auto wrappedListener = [listener](const GameEvent& baseEvt) { listener(static_cast<const T&>(baseEvt)); }; s_Events[std::type_index(typeid(T))].push_back(std::move(wrappedListener)); } std::unordered_map<std::type_index, std::vector<std::function<void(const GameEvent&)>>> EventManager::s_Events;
此方案优势:
- 无需维护事件的
name字段,减少人为错误。 - 类型索引的哈希与比较效率更高,避免事件对象的拷贝。
- 广播时直接指定事件类型,语义更清晰。
内容的提问来源于stack exchange,提问作者guyus15
相关产品推荐
相关产品推荐

