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;
};

このセットアップでは、すべてのリスナーが仮想的に 1 つの基本クラスから派生します。リスナーには複数のコールバックを登録でき、ディストリビューターは連鎖できます。もちろん、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 に答える