我正在使用C ++进行事件处理并处理事件通知,我有一个类EventGenerator,任何生成事件的类都可以继承。 EventGenerator有一个方法,其他类可以用来添加回调,一个方法在事件发生时调用回调
为了处理不同类型事件的通知,我在模板类型T上对EventGenerator进行了参数化,然后通知程序类可以在不同类型的参数化上多次从EventGenerator继承。
为了完整起见,这里是EventGenerator的代码
#ifndef _EventGenerator
#define _EventGenerator
#include <list>
#include "EventListener.h"
template <class Event>
class EventGenerator {
private:
std::list<EventListener<Event>*> listeners;
protected:
EventGenerator() {}
void changeEvent(Event event) {
std::list<EventListener<Event>*>::const_iterator it = listeners->begin();
for (; it != listeners->end(); it++) {
(*it)->changeEvent(event);
}
}
public:
void addListener(EventListener<Event>* listener) {
listeners->push_back(listener);
}
};
#endif
这里是EventListener的代码,任何想要添加回调的类都继承自 -
#ifndef _EventListener
#define _EventListener
template <class Event>
class EventListener {
private:
EventListener(const EventListener<Event>& event);
protected:
EventListener() {}
public:
virtual void changeEvent(Event event) = 0;
};
#endif
我觉得这不是一个非常好的设计,并且想知道是否有更好的设计来解决这个问题。
编辑:我正在使用多重继承这一事实令人烦恼。我经常被警告不要使用它,所以我想我想看看这样的设计是否会导致将来发生的坏事
由于
答案 0 :(得分:2)
谨防钻石继承heirarchies。还要注意,重载虚函数是件坏事。所以,如果你有这样的事情:
class Handler : public EventHandler<int>, public EventHandler<string> { ... };
将调用哪个changeEvent()函数?不要指望它!
如果你小心上面的代码应该没问题,但如果你想完全避免继承,那么我建议使用与一些唯一标识符相关联的函数引用。举个例子:
class Listener { public: virtual ~Listener ( ) { } };
template<typename Event> class Distributor : public Listener {
public:
void addListener (shared_ptr<Listener>, function<void (Event)>);
void listen (Event e) {
for_each(_listeners.begin(), _listeners.end(),
bind(&ListenNode::listen, _1, e));
}
private:
struct ListenNode {
weak_ptr<Listener> listener;
function<void (Event)> callback;
void listen (Event e) {
shared_ptr<Listener> l = listener.lock();
if(l) callback(e);
}
};
list<ListenNode> _listeners;
};
通过此设置,所有侦听器都可以虚拟地从一个基类派生。监听器可以注册多个回调,并且可以链接分销商。当然,您不必使用shared_ptr,但我喜欢它们,因为它们可以避免取消注册侦听器的麻烦。您可以按照自己喜欢的方式注册回调,将它们与字符串,整数或其他任何内容相关联。
我省略了很多细节,事件分发是一项复杂的业务。我认为Andrei Alexandrescu写了一篇关于这个主题的详细文章,请查阅。
答案 1 :(得分:1)
正如其他人所说,你可能会遇到钻石继承问题。此外,从事件处理程序库继承可能违反单一责任原则。我要做的是在需要知道某个事件的类中使用嵌套类:
class CNeedsToHandleEvent {
//...
private:
void OnChange (Event event) {
//Do processing of the event
}
class ChangeEventHandler : public EventListener {
virtual void changeEvent(Event event) {
CNeedsToHandleEvent* parent = OUTERCLASS(CNeedsToHandleEvent, m_ChangeEventHandler);
parent->OnChange(event);
}
} m_ChangeEventHandler;
friend class ChangeEventHandler;
};
这是OUTERCLASS宏。有些人可能认为它的使用有争议,也许它可能有可移植性问题,但它非常方便:
// Get a pointer to outer class of a nested class
#ifndef OUTERCLASS
#define OUTERCLASS(className, memberName) \
reinterpret_cast<className*>(reinterpret_cast<unsigned char*>(this) - offsetof(className, memberName))
#endif
您始终可以使用指向父类的指针来实例化嵌套类,以便它可以调用实际处理程序而不是依赖宏。
答案 2 :(得分:0)
这是糟糕的设计,因为在你的情况下,当你发送事件时,事情可能不会起作用......除非你有一些聪明的元编程方法来迭代你派生的所有类型。
总的来说,我认为一般来说,或者这种类型的多重继承没有任何问题。
答案 3 :(得分:0)
当您创建使用事件的多个类派生的类时,如果这些父类使用相同的事件,您可能会遇到问题。
另外请注意,您需要使用以下语法来定义抽象回调函数:
template<class T>
class Base
{
virtual void listener() = 0;
}
class Derived
: public class Base<int>
, public class Base<float>
{
void Base<int>::listener(){...}
void Base<float>::listener(){...}
}
如果你需要调用Derived中的任何一个,你可能需要使用类似的语法(虽然我真的不知道),但如果你从Base或Base引用它,它都会工作很好。另外,除非实际定义了侦听器功能,否则不会编译。
或多或少,多重继承意味着您需要更多关注,但它是该语言的一个有价值的功能。我认为可能还有更多的模板诡计可以防止钻石继承,但代价是优雅。