C++のラムダ式についてわかりやすく詳しく解説

C++において、ラムダ式は非常に便利な機能です。

この記事では、C++のラムダ式についてサンプルコードを交えながらラムダ式の基礎から応用までを解説していきます。

目次

ラムダ式とは何か

ラムダ式とは、C++11から導入された無名関数のことです。

通常の関数と同様に、引数を受け取り、戻り値を返すことができますが、ラムダ式は名前を持たず、その場で定義されるため、「無名関数」と呼ばれます。

わざわざ名前付きの関数を定義するまでもないけど、繰り返し行う処理だから一時的に関数化したいといった場面で役立ちます。

ラムダ式の基本構文

ラムダ式は、関数を定義するような構文で書かれます。

[capture list] (parameters) -> return_type {
    // 処理
}
  • capture list: ラムダ式が参照する変数を指定します。省略可能です。
  • parameters: ラムダ式の引数を指定します。省略可能です。
  • return_type: ラムダ式の戻り値の型を指定します。省略可能です。

例えば、以下のようなラムダ式があります。

auto func = [](int x, int y) -> int {
    return x + y;
}

このラムダ式は、2つの引数xとyを受け取り、それらを足した結果を返す関数として使われます。

通常、ラムダ式の外にある変数を使用するには引数として渡す必要がありますが、capture listにラムダ式内で使用する変数や定数を指定することで、ラムダ式内で自由に扱えるようになります。

int a = 10;
auto func = [a]() {
    std::cout << "a = " << a << std::endl;
};

func();
a = 10

この場合、capture list[a]と指定されているため、ラムダ式内では引数の有無にかかわらず変数aに値渡しされたコピーを使用できるようになります。

ラムダ式の使い方

ラムダ式は、関数オブジェクトとして利用することができます。また、STLアルゴリズムやスレッド処理でも利用されます。

関数オブジェクトとしてのラムダ式

関数オブジェクトとは、関数をオブジェクト化したものです。

C++では、関数ポインタやファンクタ(関数オブジェクト)を使用することができますが、ラムダ式も関数オブジェクトとして利用することができます。

以下は、ラムダ式を使用した簡単な例です。

auto func = [](int x, int y) { return x + y; };
int result = func(1, 2); // resultには3が代入される

このように、[]内にキャプチャ変数を指定し、引数を取る無名関数を定義することができます。上記の例では、2つの引数を受け取り、それらを加算して返す無名関数を定義し、funcに代入しています。

STLアルゴリズムでのラムダ式の利用

STLアルゴリズムでは、様々な操作が可能です。その中でも特に便利なのがラムダ式を使用した操作です。

以下は、vectorコンテナ内の要素全てに対して2倍した値を出力する例です。

std::vector<int> vec{1, 2, 3};
auto twice_f = [](int& n){ std::cout << n * 2 << " "; };
std::for_each(vec.begin(), vec.end(), twice_f);
// 出力結果: 2 4 6

このように、std::for_each()メソッド内でラムダ式を使用することで簡単に要素全てに対して操作することができます。

スレッド処理でのラムダ式の利用

スレッド処理では、複数のスレッド間で同時に実行される処理を記述する必要があります。

その際にもラムダ式は便利なツールとなります。

以下は、スレッド処理内でラムダ式を使用した例です。

#include <iostream>
#include <thread>

void thread_func(int x)
{
    std::cout << "Thread start" << std::endl;
    auto func = [](int x){ return x * 2; };
    int result = func(x);
    std::cout << "Result: " << result << std::endl;
}

int main()
{
    std::thread th(thread_func, 10);
    th.join();
}

このように、スレッド処理内部でラムダ式を使用し計算結果を表示させることが出来ました。

ラムダ式の注意点

ラムダ式を使用する際には、いくつかの注意点があります。

キャプチャ変数の寿命

ラムダ式でキャプチャした変数は、ラムダ式が実行される間だけ有効です。

つまり、ラムダ式が終了するとキャプチャした変数は破棄されます。そのため、キャプチャした変数を参照する場合には注意が必要です。

#include <iostream>

int main() {
    int x = 10;
    auto f = [x]() { std::cout << x << std::endl; };
    f(); // 10
    x = 20;
    f(); // 10
}

上記の例では、xをキャプチャしています。しかし、f()を呼び出す前にxの値を変更しても、f()内で表示される値は常にラムダ式が定義された時点のxの値になります。

これは、ラムダ式が実行される時点でのxへの値がコピーされて保存されているためです。

mutable指定子

デフォルトでは、ラムダ式内でキャプチャした変数は読み取り専用となります。しかし、mutable指定子を使用することで書き込み可能な変数として扱うことができます。

#include <iostream>

int main() {
    int x = 10;
    auto f = [x]() mutable { std::cout << ++x << std::endl; };
    f(); // 11
    std::cout << x << std::endl; // 10
}
11
10

上記の例では、mutable指定子を使用してキャプチャした変数xを書き込み可能なものとして扱っているため、インクリメントを行っています。

ですが、ラムダ式内でインクリメントされたxは値渡しの際にコピーされた別のxであるため、元々の変数x自体は書き換わっていません。

元の値にも変更を反映させたい場合は参照渡しを行います。

#include <iostream>

int main() {
    int x = 10;
    // [x] を [&x] にする
    auto f = [x]() mutable { std::cout << ++x << std::endl; };
    f(); // 11
    std::cout << x << std::endl; // 10
}
11
11

参照渡しされた場合は、元のxが参照されるため、ラムダ式内での値の書き換えがもとの値にも反映されるようになります。

目次