templateでObserverパターン
20091109 修正
ListenerCollection::Remove()内のstd::vector::erase()にconst_iteratorを渡していたのをiteratorに修正。
vector::erase - C++ Referenceをみる限り、const_iteratorで通らなそう。書いたのが結構前だけど、(VC9で)通してから貼り付けたつもりだったのに。
C++ テンプレートテクニックを読んでいたせいで、テンプレート厨っていう病気にかかっている最中です。
ちょうどObserverを使う機会があり、実装してみたってわけで。
実装編
#include <vector> #include <iostream> #include <string> #include <algorithm> /** 更新の通知を受けるリスナーの基底クラス */ class UpdateListener { public: // コンストラクタ UpdateListener(){} // デストラクタ virtual ~UpdateListener(){} // 更新を受け取る virtual void OnUpdate(const std::string& message) = 0; }; class HogeAUpdateListener : public UpdateListener { public: // 更新を受け取る void OnUpdate(const std::string& message){ std::cout << "HogeAUpdateListener " << message << std::endl; } }; class HogeBUpdateListener : public UpdateListener { public: // 更新を受け取る void OnUpdate(const std::string& message){ std::cout << "HogeBUpdateListener " << message << std::endl; } }; /** リスナーを管理するクラス */ template< class ClassType, typename ParamType, typename void (ClassType::*FuncType)( const ParamType& ) > class ListenerCollection { private: typedef std::vector<ClassType*> Container; public: // リスナーの追加 void Add( ClassType *pListener ) { m_list.push_back( pListener ); } // リスナーの削除 void Remove( const ClassType* pListener ) { Container::iterator endIte = std::remove( m_list.begin(), m_list.end(), pListener ); m_list.erase( endIte, m_list.end() ); } // 全てのリスナーに通知する void NotifyAll( const ParamType& message ) { for( Container::iterator ite = m_list.begin(); ite!=m_list.end(); ++ite ){ ((*ite)->*FuncType)( message ); } } private: Container m_list; }; int main(int argc, char* argv[]) { ListenerCollection<UpdateListener, std::string, &UpdateListener::OnUpdate > updateListenerCollection; HogeAUpdateListener listener1; HogeBUpdateListener listener2; updateListenerCollection.Add( &listener1 ); updateListenerCollection.Add( &listener2 ); updateListenerCollection.NotifyAll( "hogehoge" ); updateListenerCollection.Remove( &listener1 ); updateListenerCollection.NotifyAll( "hogehoge2" ); return 0; }
結果はこんな感じになる。
HogeAUpdateListener hogehoge HogeBUpdateListener hogehoge HogeBUpdateListener hogehoge2
考察編
今回の実装は、引数を一つ取るリスナーをキックできるクラスでテンプレート引数は三つ。
ClassTypeはリスナークラス。ParamTypeはそのクラスが取る引数の型。最後は関数ポインタ。
リスナークラスがParamTypeに当たるのもの提供してくれれば、もう一つテンプレート引数を減らせる。
目標としては、
- リスナーのキックする関数名を固定にしたくない
- テンプレート引数は少なく
- ある程度現実的な実装
だったので、だいたいクリアできたかな?まぁ動くものが出来てよかった。
このクラスがどう使われるかによって、内部のコンテナは変更されるべきだと思う。それとリスナーの削除について少し考えてみた。
削除のプランを三つ考えた。
- コンテナをvectorにして追加と参照を早くする
- コンテナをlistにして途中の要素の削除を早くする
- コンテナをvectorにしてオブジェクトを削除したらNullObjectを入れて、すぐに削除しない。
メモリの使い方とどの要素(追加、削除、参照)にウェイトを置くかで選択か変わるんだろうな。
今回は参照が多いと想定してる。
SingletonのテンプレートはGamePrograming Gemsで見たんだけども、他にはどんなパターンがテンプレート化できるんだろう(ってかする価値があるんだろう)
Factoryとかどうしようもないだろうし、他にもかけそうならトライしてみよう。
最後に
そろそろModern C++を読む体力もついたんじゃないだろうかと過信してる。今週は頑張って読もう。