LoginSignup
21
20

More than 5 years have passed since last update.

Strategy PatternでC++の大づかみ

Last updated at Posted at 2016-10-27

初めに

  • Head First Design Patternの題材をベースにしています。

  • 後半はテンプレートを取り扱っているので、動的実行の観点から、Strategy Patternの定義からは外れていますが、流れがいいのでそのまま取り扱っています。

  • コード確認の環境は、wandbox C++11です。

  • いろいろ突っ込みすぎたので、完全に自分の備忘録と化してしまっていますが、キーワードだけ拾っておくと、

    • オブジェクト指向(仮想関数)
    • 関数ポインタ(std::function, ラムダ式、ファンクタ)
    • テンプレート(静的な多態、ポリシー、遅延評価)  

もちろん、C++のすべての項目を拾ったわけでないのでご容赦を。

  • C++はやっぱり難しいので、間違っていたら教えてください。(スマートポインタについては、書くとぼろが出そうなので、未使用で通しました)

Strategy Pattern

とは

https://ja.wikipedia.org/wiki/Strategy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

Strategyパターンは、アルゴリズム(振る舞い)を実行時(動的)に選択することができるデザインパターンです

オブジェクト指向 的アプローチ

古典的なStrategy PatternのC++の実装をみます:

動的に、という部分をC++では1仮想関数という動的結合の仕組みを使って表現します。これによって、実行時に、派生クラスに応じてオブジェクトの振る舞いを変えることができます(多態性:ポリモーフィズム)。ここでいう振る舞いとは、メンバ関数のこと(下記の例では、quackメンバ関数)です。

#include <iostream>
using namespace std;

//QuackBehavior インターフェース
class QuackBehavior
{
public:
    virtual ~QuackBehavior() {}
    virtual void quack() const = 0; //純粋仮想関数(派生クラスに)
};

//QuackBehaviorインターフェースを実装したQuack, Squeak, MuteQuack クラス
class Quack : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "gua-gua" << endl;
    }
};
class Squeak : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "kyu-kyu" << endl;
    }
};

//なにもしない(鳴かない)クラス(Nullオブジェクト)
class MuteQuack : public QuackBehavior
{
public:
    void quack() const
    {
        cout << "<<mute>>" << endl;//何もしていないつもりだが、確認のため、出力する
    }
};

// Duck(基底クラス)と種々の派生クラス
class Duck
{
protected:
    QuackBehavior* q;
public:
    virtual ~Duck() {delete q;};
    //virtual を入れなかったのは、Duckクラス共通の振る舞いだから
    //デフォルトの実装と派生クラス独自の実装を分けたい場合は、virtualを付ける
    void quack() const
    {
        q->quack();
    }
};

class RedHeadDuck : public Duck
{
private:
public:
    RedHeadDuck()
    {
        q = new Quack();
    }
};

class RubberDuck : public Duck
{
private:
public:
    RubberDuck()
    {
        q = new Squeak();
    }
};

class DecoyDuck : public Duck
{
private:
public:
    DecoyDuck()
    {
        q = new MuteQuack();
    }
};


int main(void)
{
    //基底クラスにアップキャスト
    Duck* d1 = new RedHeadDuck();
    Duck* d2 = new RubberDuck();
    Duck* d3 = new DecoyDuck();

    //インスタンスの派生クラスに応じて、実行時にquackの動作を変化させることができる
    d1->quack();
    d2->quack();
    d3->quack();

    delete d1;
    delete d2;
    delete d3;

    return 0;
}

動作的には、以下のような感じです。

Duck* d = new RedHeadDuck();
//RedHeadDuck()
//  //基底クラスにアップキャスト
//  QuackBehavior* q = new Quack();

// [オブジェクト] [-> : アロー演算子] [メソッド(引数1..)]
d->quack(); 
//  q->quack(); // qはQuackBehavior*型でquackメンバ関数は仮想関数なので、派生クラス(Quack)のquackメンバ関数が動的に実行される。

純粋仮想関数、(単なる)仮想関数、仮想でない関数の違いは、Effective C++ 34項に詳しいですが、使い分けは、

  • 純粋仮想関数:基底クラスが抽象クラスとしての役割を担い、派生クラスのみ具体的な実装を持ちたいとき。
  • (単なる)仮想関数:基底クラスの実装をデフォルトとして、派生クラスにも独自の実装を持ちたいとき
  • 仮想でない関数:派生クラスに共通の(派生クラス側で変えてほしくない)実装をしたいとき。基底クラス側でメンバ関数定義する。)

みたいな感じです。

尚、仮想でない関数を間違って派生クラスのメンバ関数内とかに書いてしまうと、オブジェクト指向の世界でよく言われるis-a関係が崩れてしまうので2、ダメです3。実は、C++11以前はコンパイル時に警告を出してくれません4

overrideキーワードもあり、こちらは、非仮想関数の同名のシグネチャ(引数の型と戻り値の方のセット)にoverrideキーワードを用いると、コンパイルエラーにしてくれます。仮想関数にoverrideを付けるとオーバーライドしましたよ、ということを明示したことになります。

関数ポインタ的アプローチ

いままでは、仮想関数によって、動的に振る舞いを変えてきました。しかし、鳴き声を変えたいだけなのに、基底クラスQuackBehavorを使って派生クラスを作成するのは正直めんどくさいです(コードが無駄に長くなる)。そこで、関数を直接渡す方法をC++で検討してみましょう。

#include <iostream>
#include <map>
#include <vector>
using namespace std;
#include <functional> 
using std::function;



namespace QuackBehavior
{
    static void quack() { cout << "gua-gua-" << endl; }
    static void squeak() {cout << "kyu-kyu" << endl; }

    //ファンクタ
    struct QuackFunctor {
            // 引数を表示するだけの関数オブジェクト
            void operator()() {
                    std::cout << "mmm..." << std::endl;
            }
    };
}

class Duck
{
private:

public:
    //関数ポインタを用いて
    void quackUsingPointer(void (*algorithm)(void))
    {
        algorithm();
    }
    //std::functionを用いて
    void quackUsingFunctional(function<void()> algorithm)
    {
        algorithm();
    }

};

int main(void)
{
    Duck* d1 = new Duck();

    //関数ポインタを用いて
    d1->quackUsingPointer(QuackBehavior::squeak);

    //関数ポインタを渡せる
    d1->quackUsingFunctional(QuackBehavior::quack);

    //Functorも引数に渡せる
    d1->quackUsingFunctional(QuackBehavior::QuackFunctor());

    //引数に直接ラムダ式を用いてもかける。
    //[キャプチャリスト](パラメータリスト) { 関数の本体 }
    d1->quackUsingFunctional([](){cout << "q----k" << endl;});

    cout << "SEQ" << endl;

    for(int i = 0; i < 4; i++)
    {
        //[&]:引数i等の環境にある変数を参照できる(参照キャプチャ)
        //More Effective C++ 31項に「デフォルトのキャプチャモードは避ける」という文があるのですが、
        //今回は参照キャプチャしても問題ないため、使用しています。
        d1->quackUsingFunctional(
            [&](){cout << "gua-gu" << static_cast<char>('A' + i) << " [ 3 seconds]" << endl;}
        );
    }


    return 0;
}

C++で関数ポインタを扱う際は、std::functionを用います。関数ポインタの上位互換みたいなやつで、関数、ラムダ式、ファンクタ(関数オブジェクト)などを統一して扱えます。

ちなみに、ラムダ式

auto f = [](){cout << "q----k" << endl;}

は、ファンクタのsyntax sugarです5 (文法的に簡単になっているかと言われれば??ですが):

struct F {
  auto operator()() const
  {
     cout << "q----k" << endl;
  }
};

テンプレート的アプローチ

Strategy patternは、アルゴリズム(振る舞い)を実行時(動的)に選択することができるデザインパターンでした。
例えば、テキストから数値に対応した鳴き声を呼び分ける時などは、実行時(動的)に派生クラスを決定する必要があります6

#include <iostream>
using namespace std;

//QuackBehavior インターフェース
/*<<省略>>*/


/*標準入力(stdin)は
Mute
Quack
Squeak
Mute
Quack
Mute
Quack
とする。
    */
int main(void)
{
    std::string s;
    while(cin >> s)
    {
        QuackBehavior* q;
        //実行時に派生クラスの型が振り分けられるようにする
        switch(s[0])
        {
            case 'Q': q = new Quack(); break;
            case 'S': q = new Squeak(); break;
            case 'M': q = new MuteQuack(); break;
            default: delete q; continue;
        }
        q->quack();

        delete q;
        cin.ignore(); // 次の改行文字まで(改行文字を含めて)最大1文字読み捨てる。
    }
    return 0;
}

しかし、必ずしも実行時評価が必要ない場面があります。例えば、APIのユーザー側(フレームワーク使用者側)が(main関数内部で)関数を使用する状況で、実行時に動的に関数を呼び分ける必要性が特にない場合。コンパイル時に振る舞いを決められる場合。

動的に処理するデメリットとしては、動的にメモリ確保する回数に応じてオーバーヘッドがかかるところと仮想関数テーブルなるものが入ってくるので、クラスのサイズが増えてしまうことです。速度を重視する場合は、実行前に(静的に)振る舞いがすでに決定されていることを利用します。処理速度を何としても優先したいとか。

1つの簡単なアプローチとしては、enum型のラベルを引数にとって、

#include <iostream>
using namespace std;

//Quack, Squeak, MuteQuack クラス(継承を取っ払った)
class Quack
{
public:
    void quack(int second) const
    {
        cout << "gua-gua :" <<  second << "[s]" << endl;
    }
};
class Squeak
{
public:
    void quack(int second) const
    {
        cout << "kyu-kyu :" <<  second << "[s]" << endl;
    }
};

//なにもしない(鳴かない)クラス
class MuteQuack
{
public:
    void quack(int second) const
    {
        cout << "<<mute>> :" <<  second << "[s]" << endl;//Do Nothing;
    }
};

//C++11 の enum classは列挙子が強く型付けされる(More Effective C++ 10項 参照)
enum class EQuack: char
{
    QUACK, SQUEAK, MUTEQUACK
};

inline void quack(EQuack e, int second)
{
    switch(e)
    {
        //case内部で変数宣言する際は、{}で囲むこと。
        case EQuack::QUACK:
            {
                auto q = Quack();
                q.quack(second);
                break;
            }
        case EQuack::SQUEAK:
            {
                auto q = Squeak();
                q.quack(second);
                break;
            }
        case EQuack::MUTEQUACK:
            {
                auto q = MuteQuack();
                q.quack(second);
                break;
            }
        default:
            cout << "Nothing" << endl;
    }
}

int main(void)
{   
    quack(EQuack::QUACK, 2);
    quack(EQuack::SQUEAK, 3);
    quack(EQuack::MUTEQUACK, 5);
    return 0;
}

とするのが思い浮かびますが、保守性に欠けます7。この場合、Policyベースのテンプレートを用いた実装を検討できます:

#include <iostream>
using namespace std;

//Quack, Squeak, MuteQuack クラス(継承を取っ払った)
/*<<上記と同じなため、省略>>*/

//Duckでないクラス(quackの引数が2つになっている)
class DummyQuack
{
public:
    void quack(int second, int count) const
    {
        for(int i = 0; i < count; i++)
        {
          cout << "<<mute>> :" <<  second << "[s]" << endl;//Do Nothing;
        }
    }
};

template<class T>
class Duck
{
private:
    T t;
public:
    inline void quack(int second) const
    {
        //テンプレートクラスTがvoid(int)型のquackという関数を定義しさえすればよい。(静的なポリモーフィズム)
        t.quack(second);
    }
};


int main(void)
{   
    auto q1 = Duck<Quack>();

    q1.quack(2);
    auto q2 = Duck<Squeak>(); q2.quack(3);
    auto q3 = Duck<MuteQuack>(); q3.quack(5);

    //DummyQuackは2変数関数版のquackしか持っていないが、クラス生成時には、メンバ関数はインスタンス化されない。(変数未使用の警告のため、warningsのチェックを外しました。)
    auto q4 = Duck<DummyQuack>();

    //quackメンバ関数が呼ばれたときに初めて、t.quack(int)が評価される(遅延評価).この場合、DummyDuckのquack関数がvoid(int, int)型であることと矛盾し、コンパイルエラーとなる。
    //q4.quack(4);
    return 0;
}

enumの例とは違って、void(int)型のquackを持つクラスNewQuackを定義しさえすれば、他の修正は不要で、フレームワーク(main部)のユーザー側がauto q = Duck<NewQuack>とするだけで新しい振る舞いを使用できます。また、変なDuckクラス(DummyDuck)を定義したときは、コンパイル時にエラーにしてくれます8

このように、
オブジェクト指向の場合は、基底クラスが存在し、基底クラスのメソッドを引き継ぎつつ、派生クラス独自の実装を行いたいならば仮想関数を利用して、動的結合するのでありました。

一方、テンプレートベースの場合は、基底クラス -- 派生クラスという、上下関係が存在しません(この場合は、QuackBehavior基底クラスがない)。テンプレートクラスTのquackメンバ関数が使用される段階で初めて、構文分析が行われます9。つまり、コンパイル時(静的)に結合されている状態です。

(C++ テンプレート完全ガイドを参照)

ちなみに、QuackBehavior群のクラスのメンバ関数をstaticにすることで、以下のようにも記述できます(C++テンプレートテクニック 第2版 のCHAPTER06 ポリシーあたりに書いてあったはず):

#include <iostream>
using namespace std;

//Quack, Squeak, MuteQuack クラス(継承を取っ払った)
class Quack
{
public:
    static void quack(int second)
    {
        cout << "gua-gua :" <<  second << "[s]" << endl;
    }
};

class Squeak
{
public:
    static void quack(int second)
    {
        cout << "kyu-kyu :" <<  second << "[s]" << endl;
    }
};

//なにもしない(鳴かない)クラス
class MuteQuack
{
public:
    static void quack(int second)
    {
        cout << "<<mute>> :" <<  second << "[s]" << endl;//Do Nothing;
    }
};

//Duckとは違うクラス(quackの引数が2つになっている)
class DummyQuack
{
public:
    static void quack(int second, int count)
    {
        for(int i = 0; i < count; i++)
        {
          cout << "<<mute>> :" <<  second << "[s]" << endl;//Do Nothing;
        }
    }
};

template<class T>
class Duck
{
public:
    inline void quack(int second) const
    {
        //テンプレートクラスTがvoid(int)型のquackという関数を定義しさえすればよい。(静的なポリモーフィズム)
        T::quack(second);
    }
};


int main(void)
{   
    auto q1 = Duck<Quack>(); q1.quack(2);
    auto q2 = Duck<Squeak>(); q2.quack(3);
    auto q3 = Duck<MuteQuack>(); q3.quack(5);

    return 0;
}

Boostとかには一切触れませんでしたが、自分があまり詳しくないので。

次やりたいこと

Observerパターンまとめたいんだよなぁ。Rx, MVCとかの基礎になっているし。

参考

書籍

url

http://melpon.org/wandbox/
https://ja.wikipedia.org/wiki/Strategy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3


  1. JavaもC#などもそうです。RubyやPythonは言語として、仮想関数の仕組みを用意していません。 

  2. 詳しくは、Effective C++ 37項を参照してください。 

  3. Rubyでは、virtualをつけなくても、派生クラス内で基底クラスと同名の関数を定義すれば、動的に振る舞いを変えることができます。基底クラスのメソッドを呼び出したいときとかは、http://qiita.com/sonots/items/7c9edfd80f867f94a61e あたりを見れば。 

  4. C++11では、finalキーワードを指定して、仮想関数のコンパイル時の検出ができます。具体的には、Duckのquack関数を virtual void quack() const finalとするとよいです(仮想関数テーブル分のコストがかかるから、解決案としてはあんまりうれしくないですが)。 

  5. http://cpprefjp.github.io/lang/cpp11/lambda_expressions.html を参考にしました。 

  6. main部にQuackBehavior*生成の実装を書かずにFactory Method Patternを用いるべきですが、本題からそれるので。 

  7. 例えば、他の鳴き声を追加したい場合は、クラス自体を追加するだけでなく、quack関数、EQuackの定義にも新たなラベル番号を付ける必要があり、めんどくさい。 

  8. もっと厳密に継承を満たすことをやりたい場合は、テンプレートの技法のひとつのCRTP(Curiously Reccursive Template Pattern)を用いるとよいです。 

  9. テンプレートを宣言した場所(Point of Definition, PoD)とテンプレートをインスタンス化した場所(Point of Instantiation, PoI)の2段階で名前の解決が行われる 

21
20
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
20