キャプチャ(capture)とは
ラムダ式中で使えるオブジェクトは引数で受け渡された変数だけでなく、例えば上記のようにstd::coutのようなグローバルなインスタンスが使えます。
また、スコープ内のautomatic変数をラムダ式内部にキャプチャする(抱き込む)ことができます。この場合は[]内にキャプチャする変数名を指定します。
#include <iostream>
int main() {
int n = 123;
auto capt_print =
[n]() { std::cout << n << " をキャプチャしています\n"; };
capt_print(); // "123 をキャプチャしています"
}
キャプチャは、ラムダ式が定義された時点で行われます。そのため、このサンプルにおいてラムダ式の定義の後でnの値を変更しても、既にキャプチャされているので、capt_print()は、"123 をキャプチャしています"を出力します。
int n = 123;
auto capt_print =
[n]() { std::cout << n << " をキャプチャしています\n"; };
n = 456; // ラムダ式の評価には影響を及ぼさない
capt_print(); // "123 をキャプチャしています"
ここで、試しにラムダ式の評価の度にnをインクリメントしてみましょう。キャプチャされた変数をラムダ式内部で変更するには、ラムダ引数の後にmutableを指定します。
#include <iostream>
int main() {
int n = 123;
auto capt_print =
[n]() mutable { std::cout << n++ << " をキャプチャしています\n"; };
capt_print(); // "123 をキャプチャしています"
capt_print(); // "124 をキャプチャしています"
capt_print(); // "125 をキャプチャしています"
std::cout << "n = " << n << std::endl;
}
…おや? キャプチャされた変数nは、ラムダ式の評価後に変更されていませんね。これは変数nの「値」がキャプチャされたからです(値キャプチャ)。ラムダ式の評価によってキャプチャされた変数を変更したいときは。「参照キャプチャ」を用います。
#include <iostream>
int main() {
int n = 123;
auto capt_print =
// ↓この'&'が参照キャプチャを意味する
[&n]() { std::cout << n++ << " をキャプチャしています\n"; };
capt_print(); // "123 をキャプチャしています"
capt_print(); // "124 をキャプチャしています"
capt_print(); // "125 をキャプチャしています"
std::cout << "n = " << n << std::endl; // "n = 126"
n = 456;
capt_print(); // "456 をキャプチャしています"
std::cout << "n = " << n << std::endl;// "n = 457"
}
ラムダ式に複数の変数をキャプチャするときは、それらを[]内に並べます。
#include <iostream>
#include <algorithm>
int main() {
int target = 5;
int count;
// targetより小さい要素の個数を数える
auto count_less =
[&count,target](int x) { if ( x < target) ++count; };
int data[] = { 1, 3, 5, 7, 2, 4, 6, 8 };
count = 0;
std::for_each( data, data+8, count_less);
std::cout << target << " より小さな数が " << count << " 個あります" << std::endl;
}
'['の直後に'='を置くと「デフォルトで値キャプチャ」、'&'を置くと「デフォルトで参照キャプチャ」を意味します。従って、上記の[&count,target]は、[=,&count]あるいは[&,target]と書いてもかまいませんし、すべての変数が値キャプチャあるいは参照キャプチャであるなら、それぞれ[=]、[&]と略記できます。
また、[]内にthisを指定することで、クラスのメンバ関数を呼び出すこともできます。
#include <iostream>
#include <vector>
#include <algorithm>
template<typename T>
class numbers {
std::vector<T> data;
void print_one(T x) const { std::cout << x << ' '; }
public:
void add(T x) { data.push_back(x); }
void print_all() const {
std::for_each(data.begin(), data.end(), [this](T x) { print_one(x); });
}
};
int main() {
numbers<int> ints;
ints.add(1); ints.add(2); ints.add(3);
ints.print_all(); // "1 2 3 "
}
まとめ
C++0Xでは、より楽に効率的なコードを書けるように多くの新たな機能が追加されました。今回紹介したラムダ式もその1つです。ラムダ式のおかげで、小さな関数オブジェクトをいちいち定義することなく、“使い捨て”感覚で定義できます。これは、従来のコーディング・スタイルを大きく変えるものになりそうです。特に<algorithm>との組み合わせによって、今までfor-loopで書いていたものの多くが簡略化されるでしょう。
Visual Studio 2010βでほんの少し未来を覗いてみませんか?
