0

私は、コールバックを発生させる可能性のあるさまざまな種類の「イベント」と、イベントおよびその種類のイベントに固有のその他のカスタム プロパティ。

特定のイベント オブジェクトを受け取ることができるイベント マネージャと、イベントが発生したときに呼び出されるリスナー関数 (サブスクリプション) があります。

一部のイベントは特定の引数をリスナー コールバックに渡しますが、引数の型はイベントの種類によって異なります。

渡されたリスナー引数 (変数) がイベント オブジェクトに対して有効であることを検証する方法はありますか?

enum class EventKind {
   first_kind, second_kind, third_kind
};

class EventBase {
public:
    virtual EventKind kind() const = 0; // each subclass returns its EventKind identifier
    virtual bool matches(const EventBase&) const = 0;
};

class FirstEvent : public EventBase {
public:
    EventKind kind() const final { return EventKind::first_kind; }
    bool matches(const EventBase&) const final; // compare kind and other properties

    // other properties unique to FirstEvent
};

// other event subclasses ...

class EventManager {
public:
    template<typename... Args>
    void add_listener(const EventBase& event, std::function<void(Args...)> listener) {
        // validation of Args... based on event.kind() goes here...
    }
    
    template<typename... Args>
    trigger(const EventBase& event, Args... args) {
       // called when event occurs
       // internal lookup in the manager is done to find any listeners connected
       // with this event object, and then we call it...
       for (auto s : subscriptions[event.kind()]) {
          if (event.matches(*(s->event))) {
              auto* ss = dynamic_cast<Sub<Args...> *>(s);
              if (ss && ss->listener) { ss->listener(args...); }
          }
       }
    }

private:
    struct SubBase {
        EventBase* event;
    };
    template<typename... Args>
    struct Sub : public SubBase {
        std::function<void(Args...)> listener;
    };

    std::map<EventKind, std::vector<SubBase *>> subscriptions;
};

後でコールバックするためのリスナー関数を格納できる作業コードが既に用意されていますが (上記と同様)、Args... パラメーター パック/変数引数の一致は、イベントでイベントがトリガーされたときにのみ行われます。 manager - もちろん、元のリスナーの引数のセットが一致しない場合、それは呼び出されません (サイレント エラーが発生します)。

リスナーが追加されたときに、イベントの種類に基づいて (できればクラス階層を使用して)、この引数のリストを検証できると便利です。誰にもアイデアがありますか?

注: 現在、C++14 / Visual Studio 2014 / gcc 4.9.2 に制限されているため、C++17 の構成要素は使用できません。

4

2 に答える 2

2

登録時に具体的なイベント タイプを要求し、イベントにリスナー タイプを提供させます。

class FirstEvent : public EventBase {
public:
    EventKind kind() const final { return EventKind::first_kind; }
    bool matches(const EventBase&) const final; // compare kind and other properties

    using listener_type = std::function<void(Foo, Bar, Baz)>;
};

template<typename Event>
using listener_t = typename Event::listener_type;

template<typename Event>
void EventManager::add_listener(listener_t<Event> listener) {
    // can only be called with something that accepts the correct args
}
于 2021-08-04T08:51:43.440 に答える
0

これは私がなんとかうまくいった解決策です - それは Event クラス階層を避けますが、うまくいくようです。

// Validation of event listener args (general case)
template<typename... Args>
inline bool validate_event_listener_args(EventKind)
{ return false; }

// Validation of event listener args (no args case)
template<>
inline bool validate_event_listener_args<>(EventKind kind)
{
    return (kind == EventKind::second_kind || ... );
}

// Validation of event listener args (for FirstEvent)
template<>
inline bool validate_event_listener_args<Foo, Bar, Qux>(EventKind kind)
{
    return (kind == EventKind::first_kind);
}

// ...

その後、 でadd_listener呼び出しvalidate_event_listener_args<Args...>(event.kind())て、false が返された場合に例外をスローできます。これは実行時の検証に役立ちます。

于 2021-08-05T00:46:01.690 に答える