1

私は最近、新しいC ++ 11標準をいじって、基本的なイベント処理システムを作成することにしました。以下のコードは、私の現在の実装の小さな例を示しています。

#include <functional>
#include <vector>
#include <iostream>

template <typename Event>
class EventBroadcaster
{
public:
    typedef std::function<void(const Event&)> Connection;

    void connect(Connection&& connection)
    {
        connections.push_back(std::move(connection));
    }

    void signal(const Event& event)
    {
        for (const auto& connection : connections)
        {
            connection(event);
        }
    }
private:
    std::vector<Connection> connections;
};

struct MouseMotion
{
    int x = 0;
    int y = 0;
};

class Input : public EventBroadcaster<MouseMotion>
{
public:
    void process()
    {
        MouseMotion mouseMotion;
        mouseMotion.x = 10;
        mouseMotion.y = 20;
        signal(mouseMotion);
    }
};

int main()
{
    int x = 0;
    int y = 0;

    Input input;

    input.connect([&](const MouseMotion& e){
        x += e.x;
        y += e.y;
    });

    input.process();

    std::cout << x << "," << y << std::endl; // Output: 10,20

    return 0;
}

Inputクラスが単一のイベントのみをブロードキャストする場合、上記のソリューションは非常にうまく機能します。ただし、クラスがイベントだけでなくイベントInputを送信できるようにしたい場合もあります。KeyPressMouseMotion

多重継承を使うことを考えました。との両方をInput継承させる。これにより、あいまいな関数を警告するコンパイラエラーが発生します。次の回答で提供されるソリューション多重継承テンプレートクラスは、保護された関数では機能しますが、クラスの外部で呼び出されるパブリック関数では機能しません。EventBroadcaster<MouseMotion>EventBroadcaster<KeyPress>signalconnectInput

多重継承に加えて、可変個引数テンプレートが私の問題を解決できるかどうか疑問に思いました。(部分的な)テンプレートの特殊化と可変個引数テンプレートの解凍を見てきました。しかし、(エレガントな)ソリューションを提供することはできませんでした。

複数のイベントタイプをサポートするための最良の方法は何でしょうか?

4

2 に答える 2

4

実装の詳細を作成EventBroadcaster<T>し、代わりにを公開しEventBroadcasters<Ts...>ます。

EventBroadcasters<Ts...>ownsEventBroadcaster<Ts>...にはテンプレートメソッドconnect<U>とがありsignal<U>、これらをに転送しEventBroadcaster<U>ます。Uにない場合、これはコンパイルに失敗するはずTs...です。

これsignal(mouseMotion)で、がに解決さsignal<MouseMotion>(mouseMotion)れ、正しいブロードキャスターに接続されます。

あなたconnectが聞くとき、あなたが正しい種類のものを使用している限り、同様にうまくいきますstd::function。そうでない場合は、聞いているタイプを渡すことができEventます。

それをさらに魔法のようにし、必要な完全な適切な過負荷解決を有効にする方法がありますが、それは本当にトリッキーになります。connectつまり、SFINAEを実行するようにインストルメントし、渡されたラムダを手動で(そのを調べてoperator())正しいstd::functionオーバーライドにディスパッチします。しかし、単に言うことができることconnect<MouseMotion>([&](MouseMotion m){...})は改善であるはずです。

(SFINAE手法はstd::function、渡されたものからリストからどのタイプを作成できるかを確認しU、そのようなものが1つあるかどうかを確認し、それを使用します。また、そのUようなstd::function(またはそのようなcvバリアント)かどうかを確認します。ばかばかしいほど難しいことではありませんが、見た目が非常に乱雑で、私の経験では、ほとんどの人はタイプスピューが好きではなく、単に指定することを好みます<MouseMotion>。)

アップデート:

次のコードブロックは、この回答の実装を提供します。

#include <functional>
#include <vector>
#include <iostream>

namespace detail
{
    template <typename Event>
    class EventBroadcaster
    {
    public:
        typedef std::function<void(const Event&)> Connection;

        void connect(Connection&& connection)
        {
            connections.push_back(std::move(connection));
        }

        void signal(const Event& event)
        {
            for (const auto& connection : connections)
            {
                connection(event);
            }
        }
    private:
        std::vector<Connection> connections;
    };

   template <typename T> struct traits
       : public traits<decltype(&T::operator())> {};

   template <typename C, typename R, typename A>
   struct traits<R(C::*)(const A&) const>
   {
       typedef A type;
   };
}

template <typename... Events>
class EventBroadcaster
   : detail::EventBroadcaster<Events>...
{
public:
    template <typename Connection>
    void connect(Connection&& connection)
    {
        typedef typename detail::traits<Connection>::type Event;
        detail::EventBroadcaster<Event>& impl = *this;
        impl.connect(std::move(connection));
    }

    template <typename Event>
    void signal(const Event& event)
    {
        detail::EventBroadcaster<Event>& impl = *this;
        impl.signal(event);
    }

    virtual void processEvents() = 0;
};

struct MouseMotion
{
    int x = 0;
    int y = 0;
};

struct KeyPress
{
    char c;
};

class Input
    : public EventBroadcaster<MouseMotion, KeyPress>
{
public:
    void processEvents()
    {
        MouseMotion mouseMotion;
        mouseMotion.x = 10;
        mouseMotion.y = 20;
        signal(mouseMotion);

        KeyPress keyPress;
        keyPress.c = 'a';
        signal(keyPress);
    }
};

int main()
{
    int x = 0;
    int y = 0;
    char c = '~';

    Input input;

    input.connect([&](const MouseMotion& e){
        x += e.x;
        y += e.y;
    });

    input.connect([&](const KeyPress& e){
        c = e.c;
    });

    input.processEvents();

    std::cout << c << " " << x << "," << y << std::endl; // Output: a 10,20

    return 0;
}
于 2013-02-22T16:43:18.920 に答える
1

それで問題がない場合は、クラスにusingディレクティブを追加し、ラムダをオブジェクトに格納することで、この問題を解決できます。KeyPressInputstd::function<>auto

class Input :
    public EventBroadcaster<MouseMotion>,
    public EventBroadcaster<KeyPress>
{
public:
    using EventBroadcaster<MouseMotion>::signal;
    using EventBroadcaster<MouseMotion>::connect;

    // ADD THESE!
    using EventBroadcaster<KeyPress>::signal;
    using EventBroadcaster<KeyPress>::connect;

    Input() {}
    virtual ~Input() {}
    void process()
    {
        MouseMotion mouseMotion;
        mouseMotion.x = 10;
        mouseMotion.y = 20;
        signal(mouseMotion);
    }
};

int main()
{
    Input input;

    int myX = 0;
    int myY = 0;

    // STORE THE LAMBDA IN A function<> OBJECT:
    std::function<void(const MouseMotion&)> onMouseMotion = [&](const MouseMotion& e){
        myX += e.x;
        myY += e.y;
    };

    // THIS WILL NOT BE AMBIGUOUS:
    input.connect(onMouseMotion);

    input.process();
    std::cout << myX << "," << myY << std::endl; // Output: 10,20

    return 0;
}

アップデート:

std::function以下は、ラムダハンドラーをオブジェクトでラップする必要がない上記のソリューションの改善です。メソッドの定義をconnect次のように変更し、SFINAEに不適切なオーバーロードの除外を任せるだけで十分です。

template<typename F>
void connect(F connection, decltype(std::declval<F>()(std::declval<Event>()))* = nullptr)
{
    connections.push_back(connection);
}

これで、次のように呼び出すことができますconnect()

input.connect([&](const MouseMotion& e){
    myX += e.x;
    myY += e.y;
    });

input.connect([&](const KeyPress& e){
    std::cout << e.c;
    });
于 2013-02-22T16:32:05.290 に答える