ラムダ式の詳細
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 85/104。
C++11で導入されたラムダ式は、インラインで定義できる匿名関数です。STLアルゴリズムで基本的なラムダ式を見たことがあるかもしれませんが、その完全な構文を理解することで、変数のキャプチャやアクセス方法の制御といった強力な機能を利用できるようになります。
ラムダ式の完全な構文は次の通りです: [capture](parameters) mutable -> return_type { body }。キャプチャ節は、ラムダがどの外部変数にどのようにアクセスできるかを決定します:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // xのコピー
auto byRef = [&y]() { y += 5; }; // yへの参照
auto allByValue = [=]() { return x + y; }; // すべてコピー
auto allByRef = [&]() { x++; y++; }; // すべて参照
auto mixed = [x, &y]() { y += x; }; // 両方の混合
byRef();
std::cout << y << "\n"; // 25
}デフォルトでは、値キャプチャされた変数はラムダ内では const になります。mutable キーワードを使用すると、これらのコピーを変更できるようになります:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // ラムダ内のコピーを変更します
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - 元の変数は変更されませんC++14では初期化キャプチャが追加され、新しい変数を作成したり、オブジェクトをラムダ内にムーブしたりできるようになりました:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // 所有権がラムダに移譲されるラムダは、コールバック、カスタムコンパレータ、またはイベントハンドラのために、個別の関数オブジェクトを定義することなく、振る舞いをパラメータとして渡す必要がある場合に、OOP(オブジェクト指向プログラミング)において特に有用です。
チャレンジ
簡単異なるキャプチャモードを持つラムダ式の威力を示すイベントハンドラシステムを構築しましょう。コールバックを保存して呼び出すシンプルなイベントディスパッチャを作成し、ラムダがさまざまな方法で外部の状態をキャプチャする方法を実演します。
コードは3つのファイルに分けて構成します:
EventDispatcher.h: イベントコールバックを管理するEventDispatcherクラスを定義します。ディスパッチャは、
std::function<void()>のstd::vectorを使用してコールバックを保存する必要があります。以下のメソッドを含めてください:addCallback(std::function<void()> callback)— リストにコールバックを追加しますfireAll()— 保存されているすべてのコールバックを順番に呼び出しますclear()— すべてのコールバックを削除します
<functional>と<vector>をインクルードする必要があります。EventDispatcher.cpp: ディスパッチャのメソッドを実装します。fireAll()メソッドは、単にすべてのコールバックを反復処理し、それぞれを呼び出す必要があります。main.cpp: 2つの入力を読み取ります:- ベースとなる数値(整数)
- 倍数(整数)
EventDispatcherを作成し、3つのコールバックを追加することで、異なるラムダキャプチャ手法を実演します:- ベースとなる数値を値渡しでキャプチャし、
Base value: [base]と出力するラムダ - 倍数を参照渡しでキャプチャし、それを1増やしてから
Multiplier after increment: [multiplier]と出力するラムダ - 0で初期化されたカウンタ変数を値渡しでキャプチャする mutable なラムダ。呼び出されるたびにカウンタを増やし、
Call count: [counter]と出力します
すべてのコールバックを追加した後、
fireAll()を2回呼び出して、複数の呼び出しにわたって異なるキャプチャモードがどのように動作するかを確認します。2回のfireAll()呼び出しの間に、区切り文字として---を出力してください。最後に、両方のラウンドが終了した後、参照キャプチャがどのように影響したかを示すために、mainからの倍数変数の最終値を出力します:
Final multiplier: [multiplier]
例えば、入力が 10 と 5 の場合:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7入力が 42 と 0 の場合:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2主要な動作に注目してください。値渡しキャプチャは元のベースを不変に保ち、参照渡しキャプチャは main 内の実際の倍数変数を変更します(呼び出しごとに累積されます)。そして、mutable なラムダのカウンタは毎ラウンド 1 と表示されます。これは、fireAll() がコールバックを値で反復処理するためです。各ラウンドで保存された std::function の新しいコピーが呼び出され、そのキャプチャされたカウンタは0から始まって1にインクリメントされるため、保存されたコールバック自体の状態は進みません。
自分で試してみよう
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: baseを値キャプチャするラムダ式を追加してください
// 次のように出力する必要があります: "Base value: [base]"
// TODO: multiplierを参照キャプチャするラムダ式を追加してください
// multiplierを1増やし、次のように出力する必要があります: "Multiplier after increment: [multiplier]"
// TODO: カウンタ(0で初期化)を値キャプチャするMUTABLEなラムダ式を追加してください
// カウンタを増やし、次のように出力する必要があります: "Call count: [counter]"
// TODO: fireAll()を呼び出して、すべてのコールバックを実行してください
// TODO: セパレータとして "---" を出力してください
// TODO: 再度 fireAll() を呼び出してください
// TODO: 最終的なmultiplierの値を出力してください: "Final multiplier: [multiplier]"
return 0;
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス