2017-06-05 7 views
3

私はライブラリの設計に取り組んできました。 1つの側面はEventManagerになります。C++イベントハンドラクラスを設計する

これまでのデザインは次のように行く:

文字列が std::stringで、機能が std::function<void()> client.onでは、そのように定義されて
Client client 
client.on(string, function); 

void Client::on(const std::string& name, const std::function<void()>& function) { 
    _eventManager->add(string, function); 
} 

そして_eventManager->add(string, function)唯一の目的は作成することです。 Eventという名前の構造体に名前と関数を格納し、新しいストラットをベクトルにプッシュします。これは、ループがベクトル配列とは、あなたがemitと呼ばれ、その構造体の中の関数を実行するときに使用する名前に等しい任意のevent.nameを見つけます _eventManager->emit("test");

今、私はこれに似た何かを行うことができます。

_eventManager->emit()で2番目の引数を送信する必要があるため、これはすべてうまく動作し、すごく素晴らしいです。 2番目の引数は不明ですが、不明ではありませんが、最初の引数に入力した文字列に基づいて複数のデータ型にすることができます。

使用例:

Client client(); 

client.on("ready", [](User user) { 
    user.test(); 
}); 

User user; 
_eventManager->emit("ready", user); //I know this does not exists but it is nearly an example. 

client.on("message_created", [](Message message) { 
    std::cout << message << std::endl; //Operator overload here 
} 

Message message("Something to print"); 

_eventManager->emit("message_created", message)); 

私が渡されている許可される複数の種類を許可するようにboost::variantを使用して考えたが、しかし、あなたはとても関数内のような何かをすることによってそれらを取得する必要があります。

client.on("ready", [](boost::variant<User, Message> args){ 
    User user = boost::get<User>(args); 
}); 

そして、あまりにも単純なものと仮定され、私はむしろ渡されるクラスを取得するために、すべてのイベント内boost::getを使用するように強制ライブラリを使用している人を持っていません。

これまで述べてきたすばらしい情報があれば、私がやりたいことのもう一つの選択肢は何でしょうか?なぜ私がやっていることをやるべきか、してはならないのかについての示唆はありますか?

これは私に長い時間を費やしてくれましたので、進んでいただきありがとうございます。

+1

が理由だけではなく、[のQt](https://www.qt.io)のような既存のライブラリを使用しないで、[SFML](https://でWWW .sfml-dev.org)、[gtk](https://www.gtk.org)またはそれに類するもの?なぜ車輪を再発明する苦痛を経験するのですか? –

+1

'on'関数テンプレートは、コールバックが受け入れる引数の型を知っています。それは、変種を取る中間サンクをインスタンス化することができ、予想されるタイプを抽出し、成功した場合は元のコールバックを呼び出します。 – Useless

答えて

2

私は変数テンプレート(C++ 14)を使用するソリューションを持っていますが、恐ろしいハックのようです。可変テンプレートは静的にしかできないという欠点があります。あなたはそれを使って暮らすことができると仮定します(すでに悪いですが)。

各タイプのメッセージ(ここではタイプ=ハンドラに渡されるパラメータのリスト)には、別々のハンドラのリストがあります。これは、このように実装することができます。

template<typename F> static map<string, vector<F>> list; 

ここFのための例はvoid(int)ある - 1つのintの引数を受け取る関数。もう1つの例はvoid(int, int) - 2つのint引数を受け取る関数です。ここで使用するデータ構造には、各メッセージ名のハンドラのvectorが格納されています。代わりにmultimapを使用することもできます。

すると、イベントハンドラは、ちょうどハンドラのリストに追加し、「登録」:イベントハンドラを検索すると、あなたが明示的に型を指定する必要があり

template<typename F> static void add_handler(string s, F f) 
{ 
    list<F>[s].push_back(f); 
} 

。これは型安全性を実装する重要な部分です。呼び出されるハンドラの型に対応するデータ構造内でのみ検索されます。

template<typename F, typename... A> static void call_handlers(string s, A... args) 
{ 
    for (F f: list<F>[s]) 
     f(args...); 
} 

使用法:

// Define event handlers 
// Their type SHOULD be explicit (not lambda), because we will reference it further 
function<void(int)> action1 = [](int k){cout << "notify user " << k << '\n';}; 
function<void(int)> action2 = [](int k){cout << "alert user " << k << '\n';}; 
function<void(int, int)> action3 = [](int a, int b){cout << "give $" << a << " to user " << b << '\n';}; 

// Register the handlers 
add_handler("good_event", action1); 
add_handler("good_event", action2); 
add_handler("bad_event", action2); 
add_handler("money_event", action3); 

// Generate events, which will call the handlers 
call_handlers<function<void(int)>>("good_event", 7); 
call_handlers<function<void(int)>>("bad_event", 8); 
call_handlers<function<void(int, int)>>("money_event", 100, 9); 

// Wrong call, but no compilation error - the handler is just not found 
call_handlers<function<void(int)>>("money_event", 99); 
関連する問題