c++でのobserverパターン
c++でイベントリスナーのような処理が必要になったので、observerパターンを汎用的な形で実装できないか試した。
subject.hpp
#ifndef __SUBJECT_HPP__ #define __SUBJECT_HPP__ #include <vector> #include <memory> #include <algorithm> template<typename... ArgTypes> class Subject { public: typedef std::function<void(ArgTypes...)> ListenerFunction; typedef std::shared_ptr<ListenerFunction> FuncPtr; private: struct ListenerEntry { int type; FuncPtr listener; }; std::vector<ListenerEntry> listeners_; public: Subject() = default; virtual ~Subject() {} void addListener(int type, FuncPtr listener) { for (auto& entry : listeners_) { if (type == entry.type && listener == entry.listener) { return; } } ListenerEntry entry; entry.type = type; entry.listener = listener; listeners_.push_back(entry); } void removeListener(int type, FuncPtr listener) { auto end = remove_if(listeners_.begin(), listeners_.end(), [type, &listener](ListenerEntry& e) -> bool { return (e.type == type && e.listener == listener); }); listeners_.erase(end, listeners_.end()); } void notify(int type, ArgTypes... args) { for (auto& entry : listeners_) { if (entry.type == type) (*(entry.listener))(args...); } } }; #endif /* __SUBJECT_HPP__ */
test.cpp
#include <iostream> #include "subject.hpp" using std::string; using std::make_shared; void listener_func(const string& msg) { std::cout << "function:" << msg << std::endl; } class ListenerFunctor : public std::unary_function<const string&, void> { public: void operator() (const string& msg) { std::cout << "functor:" << msg << std::endl; } }; /* Subjectを継承して監視対象の具象クラスを作成 */ class SubjectConcrete : public Subject<const string&> { /* 必要に応じて処理を追加 */ }; /* イベント種別の例 */ enum EventType { EventMouseMove, EventMouseDown, EventMouseUp, }; int main() { SubjectConcrete subject; typedef SubjectConcrete::ListenerFunction ListenerFunction; /* function */ subject.addListener(EventMouseMove, make_shared<ListenerFunction>(listener_func)); /* functor */ subject.addListener(EventMouseMove, make_shared<ListenerFunction>(ListenerFunctor())); subject.addListener(EventMouseDown, make_shared<ListenerFunction>(ListenerFunctor())); /* lambda */ subject.addListener(EventMouseMove, make_shared<ListenerFunction>([](const string& msg) { std::cout << "lambda:" << msg << std::endl; })); /* fire */ string s1("move event fired"); subject.notify(EventMouseMove, s1); string s2("down event fired"); subject.notify(EventMouseDown, s2); return 0; }
build
g++ -std=c++0x -o test test.cpp
実行結果
# ./test function:move event fired functor:move event fired lambda:move event fired functor:down event fired
実装のポイントとしては、
- std::functionを使い、Listenerを関数ポインタ、Functor、lamdaなどで登録できるようにした。
- Subjectをtemplateで作成しておき、Listenerの引数を変更できるようにした。
Subject< Listenerの引数 >のようにテンプレートを実体化する時に、Listenerの引数の型を指定する
現状難点としては、addListener()でListenerを指定する際、std::functionのstd::shared_ptrを渡す必要があるので、
subject.addListener(EventMouseMove, make_shared<ListenerFunction>(ListenerA()));
のように記述が若干煩雑になること。
2015/5/27
Listenerに渡していたオブジェクトをstringからconst string&に変更。