ラムダ式 [](){}
『C++』のラムダ式は、関数オブジェクトをその場で定義できる無名関数の記法です。C++11 で導入され、コールバックや STL アルゴリズムと組み合わせるとコードを大幅に簡潔に書けます。キャプチャリストによって外部スコープの変数を値または参照で取り込める点が大きな特徴です。
構文
// ========================================
// ラムダ式の基本構文
// ========================================
// [キャプチャリスト](引数リスト) -> 戻り値型 { 本体 }
// 戻り値型は省略可能です(auto で推論されます)
// 何もキャプチャしない最も単純なラムダ式です
auto greet = []() {
// 処理を記述します
};
// 引数を受け取るラムダ式です
auto add = [](int a, int b) -> int {
return a + b;
};
// 戻り値型を省略してもコンパイラが推論します
auto multiply = [](int a, int b) {
return a * b;
};
// 値キャプチャ: 変数のコピーを取り込みます
int x = 10;
auto printX = [x]() {
// x の値をコピーして使います(外部の x を変更しても影響しません)
};
// 参照キャプチャ: 変数への参照を取り込みます
int counter = 0;
auto increment = [&counter]() {
counter++; // 外部の counter を直接変更します
};
// [=] で全変数を値キャプチャ、[&] で全変数を参照キャプチャします
auto captureAll = [=]() { /* 外部変数すべてのコピーを使います */ };
auto captureRef = [&]() { /* 外部変数すべてを参照で使います */ };
// mutable: 値キャプチャした変数をラムダ内で変更できるようにします
auto mutableLambda = [x]() mutable {
x++; // コピーなので外部の x は変わりません
};
キャプチャ一覧
| キャプチャ記法 | 概要 |
|---|---|
| [] | 何もキャプチャしません。外部変数を使わないラムダ式に使います。 |
| [x] | 変数 x を値(コピー)でキャプチャします。ラムダ内で変更しても外部には影響しません。 |
| [&x] | 変数 x を参照でキャプチャします。ラムダ内の変更が外部変数に直接反映されます。 |
| [=] | スコープ内のすべての変数を値でキャプチャします。 |
| [&] | スコープ内のすべての変数を参照でキャプチャします。 |
| [=, &x] | 基本は値キャプチャで、変数 x だけ参照キャプチャします。 |
| [&, x] | 基本は参照キャプチャで、変数 x だけ値キャプチャします。 |
| mutable | 値キャプチャした変数をラムダ本体内で変更できるようにします。外部変数は変わりません。 |
サンプルコード
kiryu_lambda.cpp
// ========================================
// kiryu_lambda.cpp
// 龍が如くの登場人物を題材に、ラムダ式の
// キャプチャと STL アルゴリズムへの活用を示すサンプルです
// ========================================
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
// ========================================
// キャラクター情報を保持する構造体です
// ========================================
struct Character {
std::string name;
std::string role;
int heat; // ヒートゲージ(戦闘力の目安)です
};
int main() {
// ========================================
// 1. キャプチャなし: 単純なラムダ式です
// ========================================
auto printSeparator = []() {
std::cout << "==============================" << std::endl;
};
printSeparator();
std::cout << "龍が如く ラムダ式サンプル" << std::endl;
printSeparator();
// ========================================
// 2. 値キャプチャ: 変数のコピーを取り込みます
// ========================================
std::string protagonist = "桐生一馬";
int baseHeat = 50;
// protagonist と baseHeat をコピーしてキャプチャします
auto showHero = [protagonist, baseHeat]() {
std::cout << "主人公: " << protagonist
<< " 基本ヒート: " << baseHeat << std::endl;
};
showHero();
// 外部変数を変更してもラムダ内のコピーには影響しません
baseHeat = 999;
std::cout << "baseHeat を変更後に呼び出しても、キャプチャ済みのコピーは変わりません:" << std::endl;
showHero(); // まだ 50 が表示されます
printSeparator();
// ========================================
// 3. 参照キャプチャ: 外部変数を直接変更します
// ========================================
int totalBattles = 0;
// totalBattles を参照でキャプチャします
auto recordBattle = [&totalBattles](const std::string& winner) {
totalBattles++; // 外部の totalBattles を直接加算します
std::cout << "バトル #" << totalBattles << " 勝者: " << winner << std::endl;
};
recordBattle("桐生一馬");
recordBattle("真島吾朗");
recordBattle("錦山彰");
std::cout << "総バトル数(参照キャプチャで更新): " << totalBattles << std::endl;
printSeparator();
// ========================================
// 4. STL の sort() にラムダ式を渡します
// ========================================
std::vector<Character> characters;
characters.push_back({"桐生一馬", "主人公", 90});
characters.push_back({"真島吾朗", "狂犬", 95});
characters.push_back({"嶋野雄一", "幹部", 70});
characters.push_back({"西谷淳二", "若頭補佐", 65});
characters.push_back({"錦山彰", "主人公の弟分", 80});
// ヒートゲージの高い順に並べ替えます
std::sort(characters.begin(), characters.end(),
[](const Character& a, const Character& b) {
return a.heat > b.heat; // 降順に並べます
}
);
std::cout << "--- ヒートゲージ降順 ---" << std::endl;
for (int i = 0; i < (int)characters.size(); i++) {
std::cout << characters[i].name << "(" << characters[i].role << ")"
<< " ヒート: " << characters[i].heat << std::endl;
}
printSeparator();
// ========================================
// 5. STL の find_if() にラムダ式を渡します
// ========================================
int threshold = 85; // 閾値を値キャプチャします
auto it = std::find_if(characters.begin(), characters.end(),
[threshold](const Character& c) {
return c.heat >= threshold; // ヒートが閾値以上のキャラを探します
}
);
if (it != characters.end()) {
std::cout << "ヒート " << threshold << " 以上の最初のキャラ: "
<< it->name << "(ヒート: " << it->heat << ")" << std::endl;
}
printSeparator();
// ========================================
// 6. mutable キャプチャ: 値コピーをラムダ内で書き換えます
// ========================================
int heatLevel = 0;
// mutable を付けるとコピーした heatLevel を加算できます
// ただし外部の heatLevel は変わりません
auto chargeHeat = [heatLevel]() mutable {
heatLevel += 10;
std::cout << "ヒートチャージ中… 内部の heatLevel: " << heatLevel << std::endl;
};
chargeHeat();
chargeHeat();
chargeHeat();
std::cout << "外部の heatLevel は変化しません: " << heatLevel << std::endl;
printSeparator();
// ========================================
// 7. ラムダ式を返す関数(クロージャの活用)です
// ========================================
// 特定キャラのヒートを加算するラムダを生成します
auto makeHeatBooster = [](int bonus) {
// bonus を値キャプチャしたラムダを返します
return [bonus](const Character& c) {
std::cout << c.name << " のヒート: " << c.heat
<< " → " << c.heat + bonus <<" (+" << bonus << " ブースト)" << std::endl;
};
};
auto boostBy20 = makeHeatBooster(20);
std::cout << "--- ヒートブースト(+20)---" << std::endl;
boostBy20(characters[0]); // 真島吾朗
boostBy20(characters[1]); // 桐生一馬
printSeparator();
return 0;
}
# コンパイルします g++ -std=c++11 kiryu_lambda.cpp -o kiryu_lambda # 実行します ./kiryu_lambda ============================== 龍が如く ラムダ式サンプル ============================== 主人公: 桐生一馬 基本ヒート: 50 baseHeat を変更後に呼び出しても、キャプチャ済みのコピーは変わりません: 主人公: 桐生一馬 基本ヒート: 50 ============================== バトル #1 勝者: 桐生一馬 バトル #2 勝者: 真島吾朗 バトル #3 勝者: 錦山彰 総バトル数(参照キャプチャで更新): 3 ============================== --- ヒートゲージ降順 --- 真島吾朗(狂犬) ヒート: 95 桐生一馬(主人公) ヒート: 90 錦山彰(主人公の弟分) ヒート: 80 嶋野雄一(幹部) ヒート: 70 西谷淳二(若頭補佐) ヒート: 65 ============================== ヒート 85 以上の最初のキャラ: 真島吾朗(ヒート: 95) ============================== ヒートチャージ中… 内部の heatLevel: 10 ヒートチャージ中… 内部の heatLevel: 20 ヒートチャージ中… 内部の heatLevel: 30 外部の heatLevel は変化しません: 0 ============================== --- ヒートブースト(+20)--- 真島吾朗 のヒート: 95 → 115 (+20 ブースト) 桐生一馬 のヒート: 90 → 110 (+20 ブースト) ==============================
よくあるミス: 参照キャプチャしたローカル変数がスコープ外で無効になる
参照キャプチャ([&x])はローカル変数への参照を保持します。変数のスコープが切れた後にラムダを呼び出すと未定義動作になります。ラムダの生存期間がキャプチャ対象の変数より長くなる場合は値キャプチャを使います。
// NG: name のスコープが切れた後にラムダを呼び出しています
auto makeLambda = []() {
std::string name = "真島吾朗";
return [&name]() { // name への参照をキャプチャします
std::cout << name << std::endl;
};
}; // ← ここで name が破棄されます
auto lambda = makeLambda();
lambda(); // 未定義動作:name はもう存在しません
OK: 値キャプチャ(コピー)にするとラムダ内に name のコピーが残ります。
// OK:
auto makeLambda = []() {
std::string name = "真島吾朗";
return [name]() { // name をコピーしてキャプチャします
std::cout << name << std::endl;
};
};
auto lambda = makeLambda();
lambda(); // "真島吾朗" と表示されます
よくあるミス: mutable なしで値キャプチャした変数を変更しようとする
値キャプチャしたコピーはデフォルトで const 扱いになります。ラムダ内でそのコピーを変更するには mutable を付ける必要があります。ただし変更されるのはコピーだけで外部変数には影響しません。
// NG: mutable がないため値キャプチャした変数を変更できません
int heatLevel = 0;
auto charge = [heatLevel]() {
heatLevel++; // コンパイルエラー:const なコピーへの代入
};
OK: mutable を付けるとコピーへの変更が可能になります。
// OK:
auto charge = [heatLevel]() mutable {
heatLevel++; // コピーへの変更なので外部の heatLevel は変わりません
};
概要
ラムダ式は C++11 で導入された無名関数の記法で、[キャプチャ](引数) -> 戻り値型 { 本体 } という形式で記述します。キャプチャリストが最大の特徴であり、[x] のように書くと変数 x のコピーをラムダ内に取り込み、[&x] と書くと参照を取り込みます。[=] でスコープ内の全変数を値キャプチャ、[&] で全変数を参照キャプチャすることもできます。値キャプチャした変数をラムダ内で変更したい場合は mutable キーワードを付けますが、あくまでコピーへの変更であり外部変数は変わりません。ラムダ式は std::sort() や std::find_if() などの STL アルゴリズムと組み合わせると特に威力を発揮します。また、ラムダがラムダを返す形でクロージャとして活用することもでき、特定のパラメータを閉じ込めた関数を動的に生成できます。戻り値型は省略してもコンパイラが推論するため、多くの場合は書く必要がありません。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。