Menu
Coddy logo textTech

ラムダ式の詳細

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では初期化キャプチャ(init captures)が追加され、新しい変数を作成したり、オブジェクトをラムダ内にムーブしたりできるようになりました:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // 所有権がラムダに移譲されました

ラムダは、コールバック、カスタムコンパレータ、またはイベントハンドラのために、別個の関数オブジェクトを定義することなく、振る舞いをパラメータとして渡す必要がある場合に、OOPにおいて特に有用です。

challenge icon

チャレンジ

簡単

異なるキャプチャモードを持つラムダ式の力を示すイベントハンドラシステムを構築しましょう。コールバックを保存して呼び出すシンプルなイベントディスパッチャを作成し、ラムダがさまざまな方法で外部の状態をキャプチャできることを実証します。

コードは3つのファイルに分けて構成します:

  • EventDispatcher.h: イベントコールバックを管理する EventDispatcher クラスを定義します。

    ディスパッチャは、std::function<void()>std::vector を使用してコールバックを保存する必要があります。以下のメソッドを含めてください:

    • addCallback(std::function<void()> callback) — リストにコールバックを追加します
    • fireAll() — 保存されているすべてのコールバックを順番に呼び出します
    • clear() — すべてのコールバックを削除します

    <functional><vector> をインクルードする必要があります。

  • EventDispatcher.cpp: ディスパッチャのメソッドを実装します。fireAll() メソッドは、単にすべてのコールバックを反復処理し、それぞれを呼び出す必要があります。
  • main.cpp: 2つの入力を読み取ります:
    1. ベースとなる数値(整数)
    2. 倍数(整数)

    EventDispatcher を作成し、3つのコールバックを追加することで、異なるラムダキャプチャ手法を実証します:

    1. ベースとなる数値を値渡しでキャプチャし、Base value: [base] と出力するラムダ
    2. 倍数を参照渡しでキャプチャし、それを1増やしてから、Multiplier after increment: [multiplier] と出力するラムダ
    3. カウンタ変数(0で初期化)を値渡しでキャプチャする mutable なラムダ。呼び出されるたびにカウンタを増やし、Call count: [counter] と出力します

    すべてのコールバックを追加した後、fireAll() を2回呼び出して、複数の呼び出しにわたって異なるキャプチャモードがどのように動作するかを確認します。2つの fireAll() 呼び出しの間に、セパレータとして --- を出力してください。

    最後に、両方のラウンドが終わった後、main関数の倍数変数の最終値を出力して、参照キャプチャがどのように影響したかを示します:Final multiplier: [multiplier]

例えば、入力が 105 の場合:

Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 2
Final multiplier: 7

入力が 420 の場合:

Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 2
Final multiplier: 2

主要な動作に注目してください。値渡しキャプチャは元のベース値を変更せず、参照渡しキャプチャは main 内の実際の倍数変数を変更します(呼び出しごとに累積されます)。また、mutable ラムダのカウンタは、同じ保存されたラムダオブジェクトが呼び出されるため、呼び出しごとに値が保持され増加していく様子を観察してください。

チートシート

ラムダ式は、インラインで定義できる無名関数です。完全な構文は次のとおりです。

[capture](parameters) mutable -> return_type { body }

キャプチャ節

キャプチャ節は、ラムダがどの外部変数にどのようにアクセスできるかを決定します。

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; };            // 両方のモードを混在

mutableラムダ

デフォルトでは、値キャプチャされた変数はラムダ内では 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;
};  // 所有権がラムダに移譲される

std::function でのラムダの使用

コールバックやイベントハンドラのために、ラムダを std::function に格納します。

#include <functional>
#include <vector>

std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* xを使用 */ });
callbacks.push_back([&y]() { /* yを修正 */ });

自分で試してみよう

#include <iostream>
#include "EventDispatcher.h"

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: base を値渡し(BY VALUE)でキャプチャするラムダ式を追加します
    // 次のように出力する必要があります: "Base value: [base]"
    
    // TODO: multiplier を参照渡し(BY REFERENCE)でキャプチャするラムダ式を追加します
    // multiplier を 1 インクリメントし、次のように出力する必要があります: "Multiplier after increment: [multiplier]"
    
    // TODO: カウンタ(0 で初期化)を値渡しでキャプチャする MUTABLE なラムダ式を追加します
    // カウンタをインクリメントし、次のように出力する必要があります: "Call count: [counter]"
    
    // TODO: fireAll() を呼び出して、すべてのコールバックを実行します
    
    // TODO: セパレータとして "---" を出力します
    
    // TODO: 再度 fireAll() を呼び出します
    
    // TODO: multiplier の最終的な値を出力します: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン