C++マニアック

C++,STL,for_each の使い方

ページの末尾へ トップページへ

節電しましょう。電力の使用状況グラフ(東電)

for_each

このページでは STL のアルゴリズムである for_each の使い方について解説します。

  1. for_each は単純な繰り返し
  2. クラスオブジェクトで for_each を使用する(その 1)
  3. 省略形の使用
  4. メンバ関数に引数がある場合
  5. STL 関連本

for_each は単純な繰り返し ページの先頭へ

for_each は、vector や set などのコンテナの要素に対する単純な繰り返しを行うためのテンプレート関数です。

注:コンテナではない普通の配列にも使えます。

for_each のプロトタイプ宣言は次のようになっています。

template <class T_InputIterator, class T_Function>
T_Function for_each(
    T_InputIterator itBegin,
    T_InputIterator itEnd,
    T_Function      function
);

使い方の要点は、上記プロトタイプ宣言の function をどのように準備するかということです。itBeginitEndは、コンテナのメンバ関数 begin()、end() で得られます。まず、簡単な例を見て見ましょう。int 型の vector の要素をプリントしてみます。

#include <vector>
#include <algorithm>
#include <cstdio>
using namespace std;

struct CPrint {
    void operator()(int iValue) { printf("%d\n", iValue); }
};

int main() {
    vector<int> veciTable(3);
    veciTable[0] = 111;
    veciTable[1] = 222;
    veciTable[2] = 333;
    for_each(veciTable.begin(), veciTable.end(), CPrint());
    return 0;
}

または、VC7++ 以降では、CPrint の定義を main() 関数の中に入れてしまい、次のように for_each() の直前に定義してもコンパイルできます。 関連の深いプログラミング要素同士を近くに定義するという意味では、こちらのほうが分かりやすいと言えます。 main() の外では CPrint という名前の競合も避ける事が出来ます。

    略
int main() {
    略
    struct CPrint { void operator()(int iValue) { printf("%d\n", iValue); } };
    for_each(veciTable.begin(), veciTable.end(), CPrint());
    return 0;
}

上記では、function として CPrint という関数オブジェクトクラスを定義して、そのコンストラクタを for_each の第 3 引数として与えています。

出力は、次のようになります。

111
222
333

つまり function は、引数として配列要素のオブジェクト、上記の場合では int 型の値を受け取る関数(または、関数オブジェクト)であるわけです。型としては、この場合次のようになります。

void func(int);

まっ、簡単なのですが、この例のように for_each は、単純な繰り返しに便利に使えます。

しかし、オブジェクト中心のプログラミングを進めていくと、コンテナの要素は大抵の場合クラスオブジェクトになります。すると、for_each で必要になる function は、そのクラスの中に組み込みたいという要望が出てきます。つまり、function を定義する特別のクラスを用意するのではなく、まず先にクラスがあって、そのクラスオブジェクトの配列に対して適用する function を同クラスのメンバ関数として定義するというスタイルになります。

つまり、オブジェクト指向プログラミングで for_each をどのように使うのかというテーマについて述べる必要があるわけです。

クラスオブジェクトで for_each を使用する(その 1) ページの先頭へ

そこで、クラスオブジェクトに対して、for_each を適用する場合について解説します。まずは、クラスありき、ということで、クラスを先に定義します。

class CManiac {
public:
    int Print() { return printf("Hello Maniac!\n"); }    // これを、どう呼び出すかが問題
};

上記のようなクラスが、まずあって、それの vector などを使うときに、for_each で簡単にオブジェクトの内容をプリントすることが出来ます。

#include <vector>
#include <algorithm>
#include <functional>
#include <cstdio>
using namespace std;

int main() {
    vector<CManiac> vecmaniac(3);    // クラスオブジェクトの配列
    for_each(vecmaniac.begin(), vecmaniac.end(), mem_fun_ref(&CManiac::Print));
    return 0;
}

上記のように、for_each でクラスのメンバ関数を呼び出すときには、mem_fun_ref を使用し、その引数として、メンバ関数へのポインタ &CManiac::Print を渡します。

この場合は、Print が引数を持たないので、簡単ですね。引数がある場合には、現在のコンパイラ事情なども考慮すると、事は少し複雑になります。

省略形の使用 ページの先頭へ

次へ進む前に、for_each の省略形を定義しておきましょう。for_each は、begin と end のイテレータを指定するために最初の 2 個の引数を使っていますが、コンテナに対して適用する場合には、常に同じパタンなので、記述が面倒なだけです。そこで、次のオーバーロードを定義しておきます。

// for_each の引数を簡略化するテンプレート関数
template <typename T_container, typename T_function>
T_function for_each(T_container& rcontainer, T_function function) {
    return for_each(rcontainer.begin(), rcontainer.end(), function);
}

これで、次の二つは同じことを書いたことになります。二番目の方が明らかに簡単ですよね。

    for_each(vec.begin(), vec.end(), function); // 元の形
    for_each(vec, function);                    // 省略形、簡単

メンバ関数に引数がある場合 ページの先頭へ

さて、省略形の準備も済んだところで、早速、引数のあるメンバ関数をもつクラスを定義してみます。

class CManiac {
    int m_iHowManiac;
public:
    int SetiHowManiac(int iHowManiac) {		// 引数を一つ持つメンバ関数
        printf("SetiHowManiac(%d)\n", iHowManiac);
        m_iHowManiac = iHowManiac;
        return 0;	// 戻り値に意味は無い。
    }
};

このクラスのメンバ関数 SetiHowManiac(int) を for_each で使うには、次のようにします。ここでは、配列に入れた CManiac オブジェクトのそれぞれに、iHowManiac を 2 として呼び出してみます。

#include <vector>
#include <algorithm>
#include <functional>
#include <cstdio>
using namespace std;

int main() {
    vector<CManiac> vecmaniac(3);    // クラスオブジェクトの配列
    for_each(vecmaniac, bind2nd(mem_fun_ref(&CManiac::SetiHowManiac), 2));
    return 0;
}

上記をコンパイルして実行すると、次のような出力が得られます。

SetiHowManiac(2)
SetiHowManiac(2)
SetiHowManiac(2)

これで、何も、問題ないのですが、実際にやってみると、上記は、VC++6 では、コンパイルできませんでした。(g++ と BorlandC++5.5、VC++.Net、VC++2005 では、問題なく、コンパイル・実行が出来ます。) 業界標準である(あった) VC++6 でコンパイルできないのは少し驚きですが、これは、抵抗しても無駄なようです。

... to be continued!