C++仿JS Event/EventTarget时std::function派生类参数转换失败问题
问题解决:C++17中EventTarget兼容子类事件回调的问题
为什么会报错?
你遇到的编译错误核心原因是函数参数的类型兼容性和值传递的切片问题:
std::function<void(Event)>要求能接受任意Event类型实例,但void(*)(KeyboardEvent)只能接受KeyboardEvent,反向类型不兼容(Event并非KeyboardEvent的子类),因此无法转换。- 退一步说,即便能转换,值传递
Event时会触发对象切片,丢失KeyboardEvent的子类成员,回调也无法正确访问key属性。
解决方案1:引用传递+动态类型转换
将事件参数改为引用传递(避免切片),在回调中用dynamic_cast将基类引用转为子类引用,实现类型安全的处理:
修改后的完整代码:
#include <iostream> #include <functional> #include <string> #include <vector> class Event { public: Event(std::string _type, void* _pointer) : type(std::move(_type)), pointer(_pointer) {} std::string type; void* pointer; // 虚析构函数保证多态行为正确 virtual ~Event() = default; }; struct EventObject { std::string type; std::function<void(Event&)> listener; void* pointer; }; class EventTarget { public: static void addEventListener(std::string type, std::function<void(Event&)> listener, void* pointer){ events.push_back({std::move(type), std::move(listener), pointer}); } static void dispatchEvent(Event& event){ for (auto& eventObj : events) { if (event.type == eventObj.type && event.pointer == eventObj.pointer) { eventObj.listener(event); } } } static std::vector<EventObject> events; }; std::vector<EventObject> EventTarget::events; class KeyboardEvent : public Event { public: KeyboardEvent(std::string _type, void* _pointer) : Event(std::move(_type), _pointer), key("random key") {} std::string key; }; class App { public: static void onGenericEvent(Event& event) { printf("event.type = %s \n", event.type.c_str()); } static void onKeyDown(Event& event) { // 动态转换为KeyboardEvent引用,失败时捕获异常 try { auto& keyEvent = dynamic_cast<KeyboardEvent&>(event); printf("event.type = %s \n", keyEvent.type.c_str()); printf("event.key = %s \n", keyEvent.key.c_str()); } catch (const std::bad_cast&) { // 类型不匹配时忽略或处理错误 return; } } }; int main() { App* app = new App(); EventTarget::addEventListener("generic", &App::onGenericEvent, app); Event event("generic", app); EventTarget::dispatchEvent(event); EventTarget::addEventListener("keydown", &App::onKeyDown, app); KeyboardEvent keyEvent("keydown", app); EventTarget::dispatchEvent(keyEvent); delete app; return 0; }
关键修改点:
- 给
Event添加虚析构函数,确保多态类型转换的正确性。 - 所有事件参数改为引用传递(
Event&),避免对象切片。 - 回调函数
onKeyDown接收Event&,通过dynamic_cast转换为KeyboardEvent&后访问子类成员。
解决方案2:模板化addEventListener(更类型安全)
如果想让回调直接接收子类事件类型,无需手动转换,可以给EventTarget添加模板版本的addEventListener,内部自动包装成兼容Event&的函数:
修改EventTarget的部分代码:
class EventTarget { public: // 通用版本 static void addEventListener(std::string type, std::function<void(Event&)> listener, void* pointer){ events.push_back({std::move(type), std::move(listener), pointer}); } // 模板版本,支持子类事件类型 template<typename EventType> static void addEventListener(std::string type, void(*listener)(EventType&), void* pointer){ addEventListener(std::move(type), [listener](Event& event){ if (auto* derivedEvent = dynamic_cast<EventType*>(&event)) { listener(*derivedEvent); } }, pointer); } static void dispatchEvent(Event& event){ for (auto& eventObj : events) { if (event.type == eventObj.type && event.pointer == eventObj.pointer) { eventObj.listener(event); } } } static std::vector<EventObject> events; };
此时App的onKeyDown可以保持原有签名:
static void onKeyDown(KeyboardEvent& event) { printf("event.type = %s \n", event.type.c_str()); printf("event.key = %s \n", event.key.c_str()); }
在main中直接调用:
EventTarget::addEventListener<KeyboardEvent>("keydown", &App::onKeyDown, app);
这种方式更贴近JavaScript的使用体验,同时保证类型安全,无需在回调中手动转换类型。
内容的提问来源于stack exchange,提问作者aquawicket
相关产品推荐
相关产品推荐

