C++ でのラムダ式
Visual C++ では、ラムダ式 (ラムダと呼びます) が状態を維持し、外側のスコープで使用できる変数にアクセスできる匿名関数のように機能します。 そのために、クラスを定義し、そのクラス型のオブジェクトを生成します。 ここでは、ラムダとはどのようなものかを定義して他のプログラミング手法と比較した上で、その利点を説明し、基本的な例を示します。
ラムダについて
多くのプログラミング言語は匿名関数の概念をサポートしています。これは、本体を持つが名前を持たない関数です。 ラムダは、匿名関数に関連したプログラミング手法です。 ラムダは、暗黙的に関数オブジェクト クラスを定義し、そのクラス型の関数オブジェクトを生成します。 関数オブジェクトの詳細については、「関数オブジェクト」を参照してください。
ラムダの基本的な例として、ISO C++ 標準では、std::sort() 関数にパラメーターを渡すコンテキストで使用するラムダを示しています。
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
この記事では、この式のしくみについて説明します。
重要
ラムダは、共通言語ランタイム (CLR) によって管理されるエンティティである ref class、ref struct、value class、または value struct ではサポートされていません。
関数オブジェクトとラムダ
コードを記述する場合、特に STL アルゴリズムを使用するときに、関数ポインターおよび関数オブジェクトを使用して問題を解決し、計算を実行することがよくあります。 関数ポインターと関数オブジェクトには利点と欠点があります。たとえば、関数ポインターは、最小限の構文オーバーヘッドで済みますが、スコープ内の状態を保持しません。関数オブジェクトは、状態を保持できますが、クラス定義の構文オーバーヘッドが必要となります。
ラムダは、関数ポインターと関数オブジェクトの両方の利点を持ち、それらの欠点を回避できます。 ラムダは、関数オブジェクトのように柔軟で状態を保持できますが、関数オブジェクトとは異なり、コンパクトな構文によりクラス定義は必要ありません。 ラムダを使用すると、同等の関数オブジェクトのコードよりも使いやすくエラーが発生しにくいコードを作成できます。
次の例では、ラムダの使用と関数オブジェクトの使用を比較しています。 最初の例では、ラムダを使用して vector オブジェクト内の各要素が偶数か奇数であるかをコンソールに出力します。 2 番目の例では、関数オブジェクトを使用して同じことを行っています。
例 1: ラムダの使用
この例では、for_each 関数呼び出しに組み込まれたラムダを使用して、vector オブジェクト内の各要素が偶数か奇数であるかをコンソールに出力します。
コード
// even_lambda.cpp
// compile with: cl /EHsc /nologo /W4 /MTd
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Create a vector object that contains 10 elements.
vector<int> v;
for (int i = 1; i < 10; ++i) {
v.push_back(i);
}
// Count the number of even numbers in the vector by
// using the for_each function and a lambda.
int evenCount = 0;
for_each(v.begin(), v.end(), [&evenCount] (int n) {
cout << n;
if (n % 2 == 0) {
cout << " is even " << endl;
++evenCount;
} else {
cout << " is odd " << endl;
}
});
// Print the count of even numbers to the console.
cout << "There are " << evenCount
<< " even numbers in the vector." << endl;
}
出力
コメント
この例では、for_each 関数への 3 番目の引数がラムダです。 [&evenCount] の部分は、式の capture 句を指定します。(int n) はパラメーター リストを指定します。残りの部分は、式の本体を指定します。
例 2: 関数オブジェクトの使用
ラムダは、前の例よりも拡張するのがはるかに複雑になる場合があります。 次の例では、ラムダの代わりに、for_each 関数と関数オブジェクトを使用して、例 1. と同じ結果を生成します。 どちらの例でも vector オブジェクトに含まれる偶数の数を格納します。 操作の状態を保持するために、FunctorClass クラスはメンバー変数の参照として m_evenCount 変数を格納します。 操作を実行するには、FunctorClass が関数呼び出し演算子 operator() を実装します。 Visual C++ コンパイラは、サイズとパフォーマンスにおいて例 1. のラムダ コードと同等のコードを生成します。 ここで紹介したような基本的な問題の場合は、おそらく、より単純なラムダのデザインの方が関数オブジェクトよりも適切です。 ただし、後で大幅な機能拡張が必要となる可能性がある場合は、コードの保守が容易になるように、関数オブジェクトのデザインを使用します。
operator() の詳細については、「関数呼び出し (C++)」を参照してください。 for_each 関数の詳細については、「for_each」を参照してください。
コード
// even_functor.cpp
// compile with: /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
class FunctorClass
{
public:
// The required constructor for this example.
explicit FunctorClass(int& evenCount)
: m_evenCount(evenCount) { }
// The function-call operator prints whether the number is
// even or odd. If the number is even, this method updates
// the counter.
void operator()(int n) const {
cout << n;
if (n % 2 == 0) {
cout << " is even " << endl;
++m_evenCount;
} else {
cout << " is odd " << endl;
}
}
private:
// Default assignment operator to silence warning C4512.
FunctorClass& operator=(const FunctorClass&);
int& m_evenCount; // the number of even variables in the vector.
};
int main()
{
// Create a vector object that contains 10 elements.
vector<int> v;
for (int i = 1; i < 10; ++i) {
v.push_back(i);
}
// Count the number of even numbers in the vector by
// using the for_each function and a function object.
int evenCount = 0;
for_each(v.begin(), v.end(), FunctorClass(evenCount));
// Print the count of even numbers to the console.
cout << "There are " << evenCount
<< " even numbers in the vector." << endl;
}
出力
まとめ
ラムダは、強力で表現力豊かなプログラミング手法です。 ラムダのパーツとプロパティについては、「ラムダ式の構文」を参照してください。 プログラムでのラムダの使用方法を調べるには、「ラムダ式の例」を参照してください。