c++でのobserverパターン
Rev.2を表示中。最新版はこちら。
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(string msg)
{
std::cout << "function:" << msg << std::endl;
}
class ListenerFunctor : public std::unary_function<string, void> {
public:
void operator() (string msg)
{
std::cout << "functor:" << msg << std::endl;
}
};
/* Subjectを継承して監視対象の具象クラスを作成 */
class SubjectConcrete : public Subject<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>([](string msg) {
std::cout << "lambda:" << msg << std::endl;
}));
/* fire */
subject.notify(EventMouseMove, "move event fired");
subject.notify(EventMouseDown, "down event fired");
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()));
のように記述が若干煩雑になること。
