1

我正在处理 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



我觉得这不是一个很好的设计,我想知道是否有更好的设计来解决这样的问题。

编辑:困扰的是我使用多重继承这一事实。我经常被警告不要使用它,所以我想我想就这样的设计是否会导致将来发生不好的事情发表意见

谢谢

4

4 回答 4

3

谨防钻石继承体系。另请注意,重载虚函数是一件坏事。所以如果你有这样的事情:

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 写了一篇关于该主题的详细文章,请查阅。

于 2009-12-28T05:51:10.447 回答
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

您始终可以使用指向父类的指针来实例化嵌套类,以便它可以调用真正的处理程序,而不是依赖于宏。

于 2009-12-28T07:25:24.750 回答
0

这是糟糕的设计,因为在您的情况下,当您分派事件时,事情可能无法正常工作......除非您有一些聪明的元编程方式来迭代您派生的所有类型。

但总的来说,我不认为多重继承或这种类型有什么问题。

于 2009-12-27T23:42:30.087 回答
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 引用中调用它,它会很好地工作。作为额外的奖励,除非实际定义了侦听器函数,否则您不会编译。

或多或少,多重继承意味着你需要更多的关注,但它是语言的一个有价值的特性。我认为您还可以采取更多的模板恶作剧来防止钻石继承,但要以优雅为代价。

于 2009-12-28T06:26:18.067 回答