如何在类派生层次结构的所有步骤上调用方法?

时间:2019-03-02 16:44:59

标签: c++ casting

以这个例子为例:

#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类能够为已触发的特定事件类型调用处理程序。处理程序由maptype_index处理程序中的vector进行组织。获取特定事件类型的条目不是问题,但是如何获取来源较少的类型?

3 个答案:

答案 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)

此答案的关键字为“ 简化”和“ 封装”。

让我们从简化开始。问题代码的几个要素除了使代码变得比需要的复杂之外,没有其他用途。 (可能会有一点性能上的好处,但对此担心还为时过早。)为了更好地了解实际的解决方案,我认为包括这些改进非常有用。另一方面,这些仅与实际解决方案间接相关,因此我将不对每种解决方案提供详细的理由。

  1. _IEventHandler重命名为BaseEventHandler以符合命名要求。
  2. on()中将BaseEventHandler设为虚拟函数,以便visitEventHandler()不需要static_cast
  3. visitEventHandler()设为非虚函数,因为现在所有实现都相同。
    • 声明~IEvent()是虚拟的,以便IEvent仍然具有虚拟功能。
  4. 删除EXTENDS宏是因为(宏是邪恶的,并且)它定义的内容不再使用。

继续进行封装,让我们从EventBus的角度来看问题。此类负责触发处理程序以响应事件。它已经推断出每个处理程序想要哪个事件,并通过这些事件类型组织了这些处理程序。 这已经破坏了封装,因为总线使用了有关处理程序内部的知识。现在,他们希望我也了解事件类型之间的继承???我需要更多信息,或者您可以自己处理!

由于封装鼓励的是更少的信息,而不是更多的信息,所以让我们考虑另一个选择:自己处理!嗯,让处理程序决定是否要处理事件。这简化了EventBus,因为它不再需要关注事件类型。其包含map的{​​{1}}可以成为单个vector,其vector方法不再需要作为模板,其hook()方法可以使循环消失很难实施。权衡是事件处理程序现在需要检查事件类型。幸运的是,fire()使检查非常简单。

dynamic_cast

有关此方法的一些注意事项:

  1. 事件处理程序按其挂接的顺序触发。以前,顺序是按挂钩顺序对最派生类的处理程序,然后按按挂钩顺序对该类的直接父级的处理程序,等等。如果这是一个重要的考虑因素,请参阅我的其他答案。

  2. 此代码仍然存在一些问题,但是我愿意将它们归因于为使示例最小化而产生的工件。

  3. 无需#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)

此答案的关键字为“ 简化”和“ 委托”。

让我们从简化开始。问题代码的几个要素除了使代码变得比需要的复杂之外,没有其他用途。 (可能会有一点性能上的好处,但对此担心还为时过早。)为了更好地了解实际的解决方案,我认为包括这些改进非常有用。另一方面,这些仅与实际解决方案间接相关,因此我将不对每种解决方案提供详细的理由。

  1. _IEventHandler重命名为BaseEventHandler以符合命名要求。
  2. on()中将BaseEventHandler设为虚拟函数,以便visitEventHandler()不需要static_cast
  3. 删除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;
}

有关此方法的一些注意事项:

  1. 有很多复杂性可用来确保在派生次数较少的类的任何处理程序之前,触发派生程度最高的类的所有事件处理程序。如果这不重要,请参阅我的其他答案。

  2. 此代码仍然存在一些问题,但是我愿意将它们归因于为使示例最小化而产生的工件。

  3. 没有宏! (宏是一个红色标记,表明可能存在设计缺陷。)

  4. 我将hook()的签名更改为void hook(BaseEventHandler *handler)。虽然这意味着将eb.hook(&ehd)更改为eb.hook<EventD>(&ehd),但它为定义事件处理程序提供了更大的自由度(不需要IEventHandler模板)。可能是一笔好买卖。