以这个例子为例:
#include <iostream>
#include <typeindex>
#include <vector>
#include <map>
class _IEventHandler {}; // just for abstract template type
class IEvent {
public:
virtual void visitEventHandler(_IEventHandler *handler) = 0;
};
#define EXTENDS(type, parentType) \
public: \
using ParentClass = parentType; \
void visitEventHandler(_IEventHandler* handler) override { \
static_cast<IEventHandler<type>*>(handler)->on(*this); \
} \
template<typename Event>
class IEventHandler : public _IEventHandler {
public:
//virtual void on(Event& e) = 0;
void on(Event &e) {
std::cout << "handle " << typeid(Event).name() << std::endl;
}
};
class EventA : public IEvent {
EXTENDS(EventA, IEvent)
};
class EventB : public EventA {
EXTENDS(EventB, EventA)
};
class EventC : public EventB {
EXTENDS(EventC, EventB)
};
class EventD : public EventC {
EXTENDS(EventD, EventC)
};
class EventBus {
public:
void fire(IEvent *event) {
while (typeid(*event) != typeid(IEvent)) {
for (_IEventHandler *handler : m_handlers[typeid(*event)])
event->visitEventHandler(handler);
// Need to update event so the loop progresses. Need to upper cast?
}
}
template<typename T>
void hook(IEventHandler<T> *handler) {
m_handlers[typeid(T)].push_back(handler);
}
protected:
std::map<std::type_index, std::vector<_IEventHandler *>> m_handlers{};
};
int main() {
EventBus eb{};
IEventHandler<EventD> ehd{};
IEventHandler<EventC> ehc{};
IEventHandler<EventA> eha{};
eb.hook(&ehd);
eb.hook(&ehc);
eb.hook(&eha);
EventD eD{};
EventB eB{};
eb.fire(&eD); // need to stdout handle EventD handle EventC handle EventA
eb.fire(&eB); // need to stdout handle EventA
return 0;
}
我希望在触发IEvent时在所有中间派生类上调用on(EventX& e)
,并在抽象类IEvent上停止。
当前我没有找到解决方案,我使用类型标识查看dyn_cast,使用decltype从实例访问静态方法(是,这不是这些运算符的基本用法;并且不允许)。
摘要: 目标是构建一个支持挂钩处理程序和激发事件的事件系统。事件是分层的,派生自一个共同的祖先类。应该为处理程序调用其名义事件类型以及从中派生的所有类型。
到目前为止,EventBus
类能够为已触发的特定事件类型调用处理程序。处理程序由map
到type_index
处理程序中的vector
进行组织。获取特定事件类型的条目不是问题,但是如何获取来源较少的类型?
答案 0 :(得分:0)
这目前正在解决我的问题:
#include <iostream>
#include <typeindex>
#include <vector>
#include <map>
class _IEventHandler {}; // just for abstract template type
class IEvent {
public:
virtual void visit_IEventHandler(std::type_index _index, _IEventHandler *handler) {}
virtual std::type_index getParentTypeIndex(std::type_index index) {
return typeid(IEvent);
}
};
#define EXTENDS(type, parentType) \
public: \
using Class = type; \
using ParentClass = parentType; \
std::type_index getParentTypeIndex(std::type_index index) { \
if (index == typeid(type)) \
return typeid(ParentClass); \
else \
return ParentClass::getParentTypeIndex(index); \
} \
#define HIERARCHICAL_VISITOR(interfaceType, reelType, methodName) \
public: \
void visit##interfaceType(std::type_index _index, interfaceType* _instanceToVisit) override { \
if (_index == typeid(Class)) \
static_cast<reelType<Class>*>(_instanceToVisit)->methodName(*this); \
else \
ParentClass::visit##interfaceType(_index, _instanceToVisit); \
} \
template<typename Event>
class IEventHandler : public _IEventHandler {
public:
//virtual void on(Event& e) = 0;
void on(Event &e) {
std::cout << "handle " << typeid(Event).name() << std::endl;
}
};
class EventA : public IEvent {
EXTENDS(EventA, IEvent)
HIERARCHICAL_VISITOR(_IEventHandler, IEventHandler, on)
};
class EventB : public EventA {
EXTENDS(EventB, EventA)
HIERARCHICAL_VISITOR(_IEventHandler, IEventHandler, on)
};
class EventC : public EventB {
EXTENDS(EventC, EventB)
HIERARCHICAL_VISITOR(_IEventHandler, IEventHandler, on)
};
class EventD : public EventC {
EXTENDS(EventD, EventC)
HIERARCHICAL_VISITOR(_IEventHandler, IEventHandler, on)
};
class EventBus {
public:
void fire(IEvent *event) {
std::type_index index = typeid(*event);
while (index != typeid(IEvent)) {
for (_IEventHandler *handler : m_handlers[index])
event->visit_IEventHandler(index, handler);
index = event->getParentTypeIndex(index);
}
}
template<typename T>
void hook(IEventHandler<T> *handler) {
m_handlers[typeid(T)].push_back(handler);
}
protected:
std::map<std::type_index, std::vector<_IEventHandler *>> m_handlers{};
};
int main() {
EventBus eb{};
IEventHandler<EventD> ehd{};
IEventHandler<EventC> ehc{};
IEventHandler<EventA> eha{};
eb.hook(&ehd);
eb.hook(&ehc);
eb.hook(&eha);
EventD eD{};
EventB eB{};
eb.fire(&eD); // need to stdout handle EventD handle EventC handle EventA
eb.fire(&eB); // need to stdout handle EventA
return 0;
}
/*
handle 6EventD
handle 6EventC
handle 6EventA
handle 6EventA
Process finished with exit code 0
*/
我发现的唯一方法是在所有阶段都使用带有typeid检查的基本继承过程。我不知道是否是最好的方法,我正在寻找更好的方法;)
答案 1 :(得分:0)
此答案的关键字为“ 简化”和“ 封装”。
让我们从简化开始。问题代码的几个要素除了使代码变得比需要的复杂之外,没有其他用途。 (可能会有一点性能上的好处,但对此担心还为时过早。)为了更好地了解实际的解决方案,我认为包括这些改进非常有用。另一方面,这些仅与实际解决方案间接相关,因此我将不对每种解决方案提供详细的理由。
_IEventHandler
重命名为BaseEventHandler
以符合命名要求。on()
中将BaseEventHandler
设为虚拟函数,以便visitEventHandler()
不需要static_cast
。visitEventHandler()
设为非虚函数,因为现在所有实现都相同。
~IEvent()
是虚拟的,以便IEvent
仍然具有虚拟功能。EXTENDS
宏是因为(宏是邪恶的,并且)它定义的内容不再使用。继续进行封装,让我们从EventBus
的角度来看问题。此类负责触发处理程序以响应事件。它已经推断出每个处理程序想要哪个事件,并通过这些事件类型组织了这些处理程序。 这已经破坏了封装,因为总线使用了有关处理程序内部的知识。现在,他们希望我也了解事件类型之间的继承???我需要更多信息,或者您可以自己处理!
由于封装鼓励的是更少的信息,而不是更多的信息,所以让我们考虑另一个选择:自己处理!嗯,让处理程序决定是否要处理事件。这简化了EventBus
,因为它不再需要关注事件类型。其包含map
的{{1}}可以成为单个vector
,其vector
方法不再需要作为模板,其hook()
方法可以使循环消失很难实施。权衡是事件处理程序现在需要检查事件类型。幸运的是,fire()
使检查非常简单。
dynamic_cast
有关此方法的一些注意事项:
事件处理程序按其挂接的顺序触发。以前,顺序是按挂钩顺序对最派生类的处理程序,然后按按挂钩顺序对该类的直接父级的处理程序,等等。如果这是一个重要的考虑因素,请参阅我的其他答案。
此代码仍然存在一些问题,但是我愿意将它们归因于为使示例最小化而产生的工件。
无需#include <iostream>
#include <vector>
#include <map>
class IEvent;
/* ** Event handler ** */
class BaseEventHandler {
public:
virtual void on(IEvent &) = 0;
};
template<typename Event>
class IEventHandler : public BaseEventHandler {
public:
void on(IEvent & e) override {
// Only fire for events of the templated type.
if ( dynamic_cast<Event *>(&e) )
std::cout << "handle " << typeid(Event).name() << std::endl;
}
};
/* ** Event ** */
class IEvent {
public:
virtual ~IEvent() {} // To force run time type information (RTTI)
void visitEventHandler(BaseEventHandler* handler)
{
handler->on(*this);
}
};
class EventA : public IEvent {};
class EventB : public EventA {};
class EventC : public EventB {};
class EventD : public EventC {};
/* ** Event Bus ** */
class EventBus {
public:
void fire(IEvent *event) {
for (BaseEventHandler *handler : m_handlers)
event->visitEventHandler(handler);
}
void hook(BaseEventHandler *handler) {
m_handlers.push_back(handler);
}
protected:
std::vector<BaseEventHandler *> m_handlers{};
};
int main() {
EventBus eb{};
IEventHandler<EventD> ehd{};
IEventHandler<EventC> ehc{};
IEventHandler<EventA> eha{};
eb.hook(&ehd);
eb.hook(&ehc);
eb.hook(&eha);
EventD eD{};
EventB eB{};
std::cout << "Firing event D.\n";
eb.fire(&eD); // need to stdout handle EventD handle EventC handle EventA
std::cout << "\nFiring event B.\n";
eb.fire(&eB); // need to stdout handle EventA
return 0;
}
!很好我认为使用该标头是设计缺陷的黄旗。比红旗(例如宏)更好,但仍然表明也许有更好的做事方法。
答案 2 :(得分:0)
此答案的关键字为“ 简化”和“ 委托”。
让我们从简化开始。问题代码的几个要素除了使代码变得比需要的复杂之外,没有其他用途。 (可能会有一点性能上的好处,但对此担心还为时过早。)为了更好地了解实际的解决方案,我认为包括这些改进非常有用。另一方面,这些仅与实际解决方案间接相关,因此我将不对每种解决方案提供详细的理由。
_IEventHandler
重命名为BaseEventHandler
以符合命名要求。on()
中将BaseEventHandler
设为虚拟函数,以便visitEventHandler()
不需要static_cast
。EXTENDS
宏是因为(宏是邪恶的,并且)它定义的内容不再使用或将在其他地方定义。继续进行委派,让我们看看为什么EventBus
在遍历事件的继承树时遇到麻烦。原因很简单:EventBus
不是事件。与其尝试复制继承树,不如使用语言的现有元素。与其尝试推断有关事件的信息,不如让我们将问题交给更了解事件的人,即事件本身。
我想到的是责任转移,基本上是将EventBus::fire()
的大部分转移到事件的visitEventHandler()
。由于每个类都知道其父级,因此可以调用其父级的visitEventHandler()
版本,从而逐步扩展层次结构,直到到达末尾为止。需要权衡的是,map
的类型成为公共知识,而不仅仅是EventBus
的私有实现细节。可能是公平交易。
#include <iostream>
#include <typeindex>
#include <vector>
#include <map>
class IEvent;
/* ** Event handler ** */
class BaseEventHandler {
public:
virtual void on(IEvent &) = 0;
};
template<typename Event>
class IEventHandler : public BaseEventHandler {
public:
void on(IEvent &) override {
std::cout << "handle " << typeid(Event).name() << std::endl;
}
};
using HandlerMap = std::map<std::type_index, std::vector<BaseEventHandler *>>;
/* ** Event ** */
class IEvent {
public:
// This is not an actual event type, so there are no handlers to visit.
virtual void visitEventHandlers(HandlerMap &) = 0;
};
// Need a definition for derived classes to call:
void IEvent::visitEventHandlers(HandlerMap &) {}
template<class Base>
class EventFrom : public Base {
public:
void visitEventHandlers(HandlerMap & handlers) override
{
// Visit the handlers for this specific event type.
for (BaseEventHandler *handler : handlers[typeid(EventFrom)])
handler->on(*this);
// Visit the handlers for the parent event type.
Base::visitEventHandlers(handlers);
}
};
using EventA = EventFrom<IEvent>;
using EventB = EventFrom<EventA>;
using EventC = EventFrom<EventB>;
using EventD = EventFrom<EventC>;
/* ** Event Bus ** */
class EventBus {
public:
void fire(IEvent *event) {
event->visitEventHandlers(m_handlers);
}
template<typename T>
void hook(IEventHandler<T> *handler) {
m_handlers[typeid(T)].push_back(handler);
}
protected:
HandlerMap m_handlers{};
};
int main() {
EventBus eb{};
IEventHandler<EventD> ehd{};
IEventHandler<EventC> ehc{};
IEventHandler<EventA> eha{};
eb.hook(&ehd);
eb.hook(&ehc);
eb.hook(&eha);
EventD eD{};
EventB eB{};
std::cout << "Firing event D.\n";
eb.fire(&eD); // need to stdout handle EventD handle EventC handle EventA
std::cout << "\nFiring event B.\n";
eb.fire(&eB); // need to stdout handle EventA
return 0;
}
有关此方法的一些注意事项:
有很多复杂性可用来确保在派生次数较少的类的任何处理程序之前,触发派生程度最高的类的所有事件处理程序。如果这不重要,请参阅我的其他答案。
此代码仍然存在一些问题,但是我愿意将它们归因于为使示例最小化而产生的工件。
没有宏! (宏是一个红色标记,表明可能存在设计缺陷。)
我将hook()
的签名更改为void hook(BaseEventHandler *handler)
。虽然这意味着将eb.hook(&ehd)
更改为eb.hook<EventD>(&ehd)
,但它为定义事件处理程序提供了更大的自由度(不需要IEventHandler
模板)。可能是一笔好买卖。