無料Wikiサービス | デモページ
Linuxなどのメモ書き

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&に変更。

 


最終更新 2015/05/27 10:51:56 - kztomita
(2015/05/22 18:07:33 作成)