C++实现类JS事件系统继承模板类遇不完整类型错误如何解决
问题根源
该报错是CRTP(奇异递归模板模式)的典型限制:当Button继承Eventable<Button>时,编译器会优先实例化基类Eventable<Button>,此时Button类本身还处于不完整状态,类内部定义的Event结构体还未被编译器解析,因此基类中引用C::Event自然无法识别。
可行解决方案
方案1:抽离事件类型作为额外模板参数(最推荐,性能最高、实现最简单)
把Event结构体从Button内部移出,作为独立类型提前定义,再作为第二个模板参数传给Eventable,完全规避基类对派生类内部类型的依赖。
代码示例:
// 提前定义Button对应的事件类型 struct ButtonEvent { enum struct EventType { ButtonPressed }; EventType type; struct ButtonPressed {}; }; // 调整Eventable模板,新增事件类型参数 template <class C, class EventT> class Eventable { using CEvent = EventT; using CEventType = typename CEvent::EventType; std::list<std::function<void(const CEvent&)>> event_listeners{}; public: // 事件监听添加实现 template <typename Handler> void addEventListener(CEventType type, Handler&& handler) { event_listeners.push_back(std::forward<Handler>(handler)); } // 事件触发实现 void evoke(const CEvent& event) { for (auto& handler : event_listeners) { handler(event); } } }; // Button继承时传入事件类型 class Button: public Eventable<Button, ButtonEvent> { bool check_if_inside(const Vector2f& pos) const { // 你的碰撞检测逻辑 return true; } public: void press(const Vector2f& pos) { if(this->check_if_inside(pos)) { ButtonEvent e; e.type = ButtonEvent::EventType::ButtonPressed; this->evoke(e); } } };
方案2:利用模板成员函数延迟实例化特性
类模板的成员函数只会在被调用时才会实例化,此时派生类已经是完整类型,可以正常识别内部定义的Event。只需要把依赖C::Event的类型定义从基类的类作用域移到成员函数内部即可。
代码示例:
template <class C> class Eventable { // 类作用域不声明依赖C的类型,改用类型擦除存储回调 std::list<std::any> event_listeners{}; public: template <typename Handler> void addEventListener(typename C::Event::EventType type, Handler&& handler) { // 成员函数实例化时C已经完整,可以正常获取C::Event using CEvent = typename C::Event; event_listeners.emplace_back(std::function<void(const CEvent&)>(std::forward<Handler>(handler))); } void evoke(const typename C::Event& event) { using CEvent = typename C::Event; for (auto& handler : event_listeners) { std::any_cast<std::function<void(const CEvent&)>>(handler)(event); } } }; // Button的定义不需要调整,注意补全语法漏的分号即可 class Button: public Eventable<Button> { public: struct Event { enum struct EventType { ButtonPressed }; EventType type; struct ButtonPressed {}; }; // 其余成员逻辑 };
该方案不需要调整原有的继承写法,但需要用到类型擦除,运行时会有微小的性能开销,错误提示也不如第一种方案直观。
方案3:用类型萃取层解耦
通过专门的traits萃取类绑定派生类和对应的事件类型,基类直接从traits中获取类型,不需要依赖派生类的内部定义。
代码示例:
// 前置声明事件萃取模板 template <class C> struct EventTraits; template <class C> class Eventable { // 从萃取类获取类型,不直接依赖C的内部定义 using CEvent = typename EventTraits<C>::Event; using CEventType = typename EventTraits<C>::EventType; std::list<std::function<void(const CEvent&)>> event_listeners{}; public: // 其余实现和最初版本完全一致 }; // 前置声明Button,提前特化萃取类 class Button; template <> struct EventTraits<Button> { struct Event { enum struct EventType { ButtonPressed }; EventType type; struct ButtonPressed {}; }; using EventType = typename Event::EventType; }; // 定义Button class Button: public Eventable<Button> { // 直接复用萃取类的Event定义 using Event = typename EventTraits<Button>::Event; // 其余成员逻辑 };
另外注意你示例代码里有语法遗漏:枚举定义、类定义末尾都需要加分号,调整后即可正常编译。
内容的提问来源于stack exchange,提问作者Egor Eremeev
相关产品推荐
相关产品推荐

