Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

クラスにリスナインターフェースとオブザーバリストメンバ変数を追加するマクロ

More than 5 years have passed since last update.

概要

AndoroidSDK でコードを書いていて、クラス宣言の

class Foo extends View.OnClickListener {

みたいな書き方がカッコよかったので C++ で同じような書き方を試してみた。
そのコードテンプレートをクラスに追加するマクロを作った。

やりたいこと

こんな感じのことが c++ でしたい。

class Bar : public Foo::OnDoSomethingListener {

  void onDoSomething(const Foo& f)
  {
     // f が do something したら Bar も何かやる
  }
}

/* 何かの処理 */ {

  Bar bar1;
  Bar bar2;

  Foo foo;

  // bar1, bar2 を foo のリスナーとして登録
  foo.setUpdateListener(bar1);
  foo.setUpdateListener(bar2);

  foo.doSomething();
  // ここで bar1, bar2 の onDoSomethingListener が呼ばれる
}

やりたいこと実現の最小限テンプレート

class Foo {
public:

  //! Foo の DoSomething を監視するクラスのインターフェース
  class DoSomethingListener {
  public:
    //! アップデート受信のための関数.
    virtual void onDoSomething(const Foo & obj) = 0;
  };

  // スマートポインタをついでに typedef しておく
  typedef std::shared_ptr<DoSomethingListener>
                              PtrDoSomethingListener;

  //! リスナー登録.
  void setDoSomethingListener(const PtrDoSomethingListener& p) {
    listDoSomethingListener.push_back(p);
  }

private:

  //! Update を通知.
  void notifyDoSomething() {
    for(auto p : listDoSomethingListener) {
      p->onDoSomething(*this);
    }
  }

  //! 登録されているリスナーのリスト
  std::vector<PtrDoSomethingListener> listDoSomethingListener;
}

class Bar : public Foo::OnDoSomethingListener {
public:
  void onDoSomething(const Foo& obj) override {}
}

オブザーバリストの重複チェックとかデリート関数は省略。

実装例

コード

#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;

class Aniki {
// テンプレ
public:

    class YaranaikaListener {
    public:
        virtual void onYaranaika(const Aniki& obj) = 0;
    };

    typedef shared_ptr<YaranaikaListener> PtrYaranaikaListener;

    void setYaranaikaListener(const PtrYaranaikaListener& p) {
        listYaranaikaListener.push_back(p);
    }

private:

    void notifyYaranaika() {
        for(auto p : listYaranaikaListener) {
            p->onYaranaika(*this);
        }
    }

    std::vector<PtrYaranaikaListener> listYaranaikaListener;

// Aniki クラス個別の処理
public:

    Aniki(const string& name) : m_name(name) {}

    void yaranaika() {
        cout << m_name << ": やらないか" << endl;

        // やらないかをリスナーに通知
        notifyYaranaika();
    }

    const string& getName() const { return m_name; }

private:
    string m_name;
};

class Shatei : public Aniki::YaranaikaListener {
public:
    Shatei(string name) : m_name(name) {}


    void onYaranaika(const Aniki& obj) override
    {
        cout << m_name << ": " << obj.getName() << "兄貴ィィッ-----!" << endl;
    }

private:
    string m_name;

};

typedef shared_ptr<Shatei> PtrShatei;

int main() {

    // 舎弟を作成
    PtrShatei a(new Shatei("アドン"));
    PtrShatei s(new Shatei("サムソン"));
    PtrShatei u(new Shatei("うみにん"));

    // アニキを作成
    Aniki aniki("イダテン");

    // 舎弟を登録
    aniki.setYaranaikaListener(a);
    aniki.setYaranaikaListener(s);
    aniki.setYaranaikaListener(u);

    // やらないか
    aniki.yaranaika();

    return 0;
}

コンソール出力

イダテン: やらないか
アドン: イダテン兄貴ィィッ-----!
サムソン: イダテン兄貴ィィッ-----!
うみにん: イダテン兄貴ィィッ-----!

マクロ化

上記コードにより、クラスにリスナインタフェース&オブザーバリストを追加することが出来た。
しかし、 onClick, onDoubleClick などといった色々なイベントに対してそれぞれリスナーを追加するとなると、同じようなコードを繰り返し書かなくてはいけなくなり気持ちが悪い。

そこで、リスナ追加のためのコードは "OnXXXListener" の "XXX" 部分以外は全く同じであることを利用して下記のようなマクロを作る。

//! クラスの冒頭に書くとリスナインターフェースとオブザーバリストを追加できるマクロ
//! @param _class クラス名
//! @param _what  イベント名, On[_what]Listener というインターフェースが追加される.
#define REGISTER_LISTENER(_class, _what) \
public: \
  class _what ## Listener { \
  public: \
    virtual void on ## _what (const _class & obj) = 0; \
  }; \
\
  typedef std::shared_ptr< _what ## Listener > Ptr ## _what ## Listener; \
\
  void set ## _what ## Listener(const Ptr ## _what ## Listener& p) { \
    list ## _what ## Listener.push_back(p); \
  } \
\
private: \
  void notify ## _what () { \
    for(auto p : list ## _what ## Listener) { \
      p->on ## _what (*this); \
    } \
  } \
  std::vector<Ptr ## _what ## Listener> list ## _what ## Listener;

"##" はマクロ内で有効な文字列を結合する演算子。
例えば _what に Click が代入されると "on ## _what ## Listener" は onClickListener というコードに展開される。

使用例

コード

class Aniki {

// OnYaranaikaListener を追加    
REGISTER_LISTENER(Aniki, Yaranaika)

// OnLoveListener を追加
REGISTER_LISTENER(Aniki, Love)

public:

    Aniki(const string& name) : m_name(name) {}

    void yaranaika() {
        cout << m_name << ": やらないか" << endl;
        notifyYaranaika(); // 通知 1
    }

    void love() {
        cout << m_name << ": 愛・・・" << endl;
        notifyLove(); // 通知 2
    }

    const string& getName() const { return m_name; }

private:
    string m_name;
};

class Shatei : public Aniki::YaranaikaListener, public Aniki::LoveListener
{
public:

    Shatei(const string& name) : m_name(name) {}

    // イベント受信 1
    void onYaranaika(const Aniki& obj) override {
        cout << m_name << ": " << obj.getName() << "兄貴ィィッ-----!" << endl;
    }

    // イベント受信 2
    void onLove(const Aniki& obj) override {
        cout << m_name << ": " << "超兄貴!" << endl;
    }

private:
    string m_name;
};

typedef shared_ptr<Shatei> PtrShatei;

int main() {

    Aniki aniki("イダテン");

    PtrShatei a(new Shatei("アドン"));
    PtrShatei s(new Shatei("サムソン"));
    PtrShatei u(new Shatei("うみにん"));

    // 舎弟を登録 1
    aniki.setYaranaikaListener(a);
    aniki.setYaranaikaListener(s);
    aniki.setYaranaikaListener(u);
    // アニキがアクション 1
    aniki.yaranaika();

    cout << endl;

    // 舎弟を登録 2
    aniki.setLoveListener(a);
    aniki.setLoveListener(s);
    aniki.setLoveListener(u);
    // アニキがアクション 2
    aniki.love();

    return 0;
}

コンソール出力

イダテン: やらないか
アドン: イダテン兄貴ィィッ-----!
サムソン: イダテン兄貴ィィッ-----!
うみにん: イダテン兄貴ィィッ-----!

イダテン: 愛・・・
アドン: 超兄貴!
サムソン: 超兄貴!
うみにん: 超兄貴!

面倒な定型コードを短縮して実装部分に集中することが出来るようになった。

_meki
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away