[cocos2dx] NotificationCenter的な何かを作ったよ

 cocos2dxには、NotificationCenter(CCNotificationCenter)というObserverパターンを実装したクラスがあります。このクラスを使うと、シーンをまたいでデータの受け渡しができるようになり、非常に便利です。使ったことないけど。

 しかし、cocos2dx 3.0安定版にはこのクラスがありません、というか非推奨になっています。ソースを見た感じ代替するコードが見つからなかったので、もしかするとホントに亡くなってしまったのかもしれません…どこに行ったか知っている人は教えてね!そんなわけで、今回はこのNotificationCenterの代わりになるクラスを作りました。

Observerクラス

 以下コードを載せます。ヘッダだけなので適当にincludeして使えます。

 こんな感じ。
ドキュメントっぽいものはここの下の方に書きました(適当)。C++11の機能をバリバリ使ってるので、cocos2dx 2.x系では使えません。あしからず。

 使い方はドキュメントの通り。ObserverクラスはシングルトンなのでgetInstance()でインスタンスを取った後、addSubjectでコールバック関数を登録、notify()で通知を送ります。cocos2dxのNotificationCenterとの違いは、キーを文字列ではなく列挙型で指定する部分と、任意の引数を渡すことが出来る点です。

キーは列挙型

 なんでそんなことにしたかというと、std::mapで管理すときにハッシュ化するのが面倒だった(結局std::unordered_map使ってるんだけど)っていうのもあるんだけど、一番の理由はタイポが怖いから。あと文字列の比較とかってなんか気持ち悪いな―って。それだけです。そんなわけで、一度列挙体を定義する必要があります。

任意の引数が渡せる!

 これが大きな特徴かな。登録したコールバック関数に任意の引数を指定することが出来ます。

簡単な使い方

 詳しい使い方はドキュメントを参照してください。ここでは大雑把に雰囲気だけお伝えします。

enum class Event
{
    HyperEvent,
    MarvelousEvent
};

/* どこか関数内で */
// void (int)なラムダを登録
Observer::getInstance().addSubject<void(int)>(Event::HyperEvent, [](int value) {
    log("value is %d. HYPER!!!!!!!!!!!!!!!!!", value);
});

// void (int, int)なラムダを登録
Observer::getInstance().addSubject<void(int, int)>(Event::MarvelousEvent, [](int x, int y) {
    log("%d + %d = %d. MARVELOUS!!!!!!!!!!!!!!", x, y, x + y);
});

// HyperEventに通知送信!
Observer::getInstance().notify(Event::HyperEvent, 100);

// 引数が足りないよ!
Observer::getInstance().notify(Event::MarvelousEvent, 100); // エラー

 こんな感じ。

学んだこと

ラムダ関数

 これ、コードは結構シンプルなんだけど、思った以上に時間がかかりました。C++11勉強不足ですね。なにに時間かけたかというと、ラムダ関数の型推論周りです。
 結論から言うと、ラムダ関数は型周りが少し特殊で、戻り値や引数の型を取得することが出来ません。また、同一の処理を記述したラムダ関数でも、別々の型が返ってきます。さらに、ラムダ関数に対してdecltypeを使用することも出来ません(一度変数に書き出せば取得できます)。

decltype([](int x) { return x; }) lambda = [](int x) { return x; }; // Error!

 ↑みたいなコードはコンパイルが通りません。decltype内のラムダ関数と、右辺のラムダ関数は別の型です。

 当初はファンクタ、関数ポインタに類似の何かだと思っていたので、テンプレートクラスの部分特殊化とかで引数の型を切り離せルのではないかと模索していましたが、結果として無理であるということがわかりました。無念。ということで、初めの頃は

Observer::getInstance().addSubject(Event::HyperEvent, std::function<void(int)>([](int value) {
    log("value is %d. HYPER!!!!!!!!!!!!!!!!!", value);
}));

 こんな感じのコードを書いていたのですが、テンプレート関数の引数の型推論が思っていたよりも優秀で、一部の引数の型の指定だけでもコンパイルが通りました。簡単な使い方の項で書いたコードは、実は第二テンプレート引数に列挙型の型指定をしなければならないのですが、その部分は推論させてます。ちなみに省略できるのは後ろだけ(当たり前)。先頭を省略することは出来ないので、テンプレート引数の順序は計画的に決めましょう。
 ラムダ関数は便利だけど、制限がそこそこ多いな―って思いました。C++14とかではもう少しいい感じになるっぽいので期待しましょう。

テンプレート

 地味な感じではありますが、テンプレート周りもC++03に比べてかなり強力になったな、と感じました。TMPのためのクラスが標準に入って、いちいち既存プロジェクトからenable_ifを持ってこなくてもいいようになりました。素晴らしいです。そして、今回一番SUGEEEEEEEEEEEEEEEEEEEEEEって思ったのは、デフォルトテンプレート引数です。Observer.hでいうと53行目とかその辺です。この部分はみなさんおなじみのSFINAEです。デフォルトテンプレート引数を使ってSFINAEが出来ます。素晴らしい!実に素晴らしい!もう戻り値SFINAEでIDEの入力候補に謎の型が出現することはないんです!マーベラス!すふぃ姉かわいいよすふぃ姉!
 他にも

template<
    typename T,
    typename Type = Hoge<T>
>
Type test(T x) { return Type(); }

みたいにテンプレートの別名を作ることも出来るみたいです。これなら戻り値にちょっと複雑なテンプレートが入っても見やすい!

 他にもVariadic Templateスゲー、autoスゲー、Type Erasureスゲー、range based forベンリー、final&overrideベンリーと、C++はやっぱり凄いなーと思いました(小並感)。

結論

 SFINAE、結婚しよう!!

宣伝

 Observerクラスは「俺々ライブラリ」に含まれているクラスです。俺々ライブラリは、「俺がゲーム開発するときに得するライブラリ」です。cocos2dxで使えそうな処理群、アクション、ノード等がいっぱい入ってます(ドキュメントはない)。興味があるかたはご利用くださいな。

 Oreore/俺々 cocos2dxライブラリ

0 件のコメント :

コメントを投稿