Flat Leon Works

アプリやゲームを作ってます。

【C++ イディオム】メソッドチェイン + 引数のクラス化 = 名前付きパラメータイディオム

メソッドチェインと引数のクラス化

メソッドチェイン」はメソッド(メンバ関数)を連続で呼び出す手法です。「引数のクラス化」は増えてしまった引数をクラス化して、1個のクラスにカプセル化する手法です。この2つは独立したプログラミングの手法ですが、一緒に使うことで「名前付きパラメータ」イディオムと呼ばれる便利な手法が生まれます。まずは「メソッドチェイン」と「引数のクラス化」について紹介したいと思います。

メソッドチェインとは

a.foo().bar().baz()のようにメソッド(メンバ関数)呼び出しを連続で行うことです。これはメンバ関数*thisを返すことで実現します。

class A
{
public:
    A& foo(){ /*何か処理*/ return *this; }
    A& bar(){ /*何か処理*/ return *this; }
    A& baz(){ /*何か処理*/ return *this; }
};

メソッドチェインを利用することで、1つの式として複数メンバ関数を呼び出すことができます。この「1つの式として」というのがポイントです。通常、複数メンバ関数を呼び出すには呼び出す回数だけ文(statement)が必要になります。

// メソッドチェインを利用しない場合
A a;
a.foo(); // 文
a.bar(); // 文
a.baz(); // 文

メソッドチェインを利用すると複数メンバ関数呼び出しを1つの式として行うことができます。

// メソッドチェインを利用しない場合
A a;
a.foo().bar().baz(); // 全体で1つの式になる

1つの式なのでこういう書き方もできます。

func( A().foo().bar().baz() );

引数のクラス化

「引数のクラス化」とは複数の引数を1つのクラスにまとめて、関数の引数をそのクラス1つだけにすることです。「引数のクラス化」という用語があるわけではないのですが、この記事ではこの手法のことを「引数のクラス化」と呼ぶことにします。そしてそのクラスをパラメータクラスと呼ぶことにします。

int func( int a = 1, int b = 2, int c = 3 ){ return a + b + c; }

// ↓引数のクラス化

class FuncParams
{
public:
    FuncParams(){
        // パラメータのデフォルト値をセット
        a = 1;
        b = 2;
        c = 3;
    }
    int a;
    int b;
    int c;

    // 値のセットを楽にするユーティリティ関数
    void SetSameValue( int value )
    {
        a = value;
        b = value;
        c = value;
    }
    // プリセットで値をセットする関数
    void Preset1( void )
    {
        a = 10;
        b = 9;
        c = 8;
    }
};

int func( const FuncParams& params ){ return params.a + params.b + params.c; }

このように複数の引数がパラメータクラス1つのみになります。この手法のメリットはたくさんあります。

  • 見やすくなる
  • 関数の引数の増減への対応が簡単になる( メンバ変数の数を変えるだけ )
  • 引数をセットする順番が自由になる
  • メンバ変数名でセットすることになるので可読性が上がる
  • デフォルト値を自由に設定できる( パラメータクラスのコンストラクタでメンバ変数に値をセットしておくとそれがデフォルト値になる )
  • メンバ関数を用意しておけば、値のセットを楽にしたりプリセットを用意したりといったことが可能になる

パラメータクラスを使った関数呼び出しは以下のようになります。

int main() {
    FuncParams params;
    params.b = 5;
    params.a = 2;
    func( params );
    return 0;
}

引数のクラス化とメソッドチェインを組み合わせる

このパラメータクラスにメソッドチェインを組み合わせるとさらにエレガントな書き方が可能になります。

先ほどのパラメータクラスをメソッドチェインに対応させてみましょう。

class FuncParams
{
public:
    FuncParams(){
        // パラメータのデフォルト値をセット
        a = 1;
        b = 2;
        c = 3;
    }
    int a;
    int b;
    int c;
    FuncParams& SetA( int value ) { a = value; return *this; }
    FuncParams& SetB( int value ) { b = value; return *this; }
    FuncParams& SetC( int value ) { c = value; return *this; }
};

すると関数呼び出しを以下のようにすることができます。

int main() {
    // メソッドチェイン対応前
    FuncParams params;
    params.b = 5;
    params.a = 2;
    func( params );

    // メソッドチェイン対応後
    func( FuncParams().SetB( 5 ).SetA( 2 ) );
    return 0;
}

すっきりと1行で関数呼び出しができるようになりました。

一番最初の引数のクラス化をする前とも比較してみましょう。

int main() {
    // 普通の関数呼び出し
    func( 2, 5 );

    // 名前付きパラメータイディオムによる関数呼び出し
    func( FuncParams().SetB( 5 ).SetA( 2 ) );
    return 0;
}

パラメータクラスを利用することで、SetB、SetAなどどの値にセットしているのかがわかりやすくなっています。さらに、セットする順番も自由になっています。

この手法は「名前付きパラメータイディオム」と呼ばれるようです。参考:More C++ Idioms/Named Parameter - Wikibooks, open books for an open world

まとめ

名前付きパラメータイディオムはいいぞ。