Menu
Coddy logo textTech

Lambda-Ausdrücke im Detail

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 85 von 104.

Lambda-Ausdrücke, die in C++11 eingeführt wurden, sind anonyme Funktionen, die Sie inline definieren können. Während Sie bereits grundlegende Lambdas mit STL-Algorithmen gesehen haben, schaltet das Verständnis ihrer vollständigen Syntax leistungsstarke Funktionen frei, um Variablen zu erfassen und zu steuern, wie auf sie zugegriffen wird.

Die vollständige Lambda-Syntax lautet: [capture](parameters) mutable -> return_type { body }. Die Capture-Klausel bestimmt, auf welche externen Variablen das Lambda zugreifen kann und wie:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;
    
    auto byValue = [x]() { return x * 2; };        // Kopie von x
    auto byRef = [&y]() { y += 5; };               // Referenz auf y
    auto allByValue = [=]() { return x + y; };     // Alle als Kopie
    auto allByRef = [&]() { x++; y++; };           // Alle als Referenz
    auto mixed = [x, &y]() { y += x; };            // Beides gemischt
    
    byRef();
    std::cout << y << "\n";  // 25
}

Standardmäßig sind per Wert erfasste Variablen innerhalb des Lambdas const. Das Schlüsselwort mutable ermöglicht die Modifikation dieser Kopien:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // Modifiziert die Kopie des Lambdas
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - Original unverändert

C++14 fügte Init-Captures hinzu, was es ermöglicht, neue Variablen zu erstellen oder Objekte in das Lambda zu verschieben:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Ownership wurde in das Lambda übertragen

Lambdas sind in der OOP besonders nützlich, wenn Sie Verhalten als Parameter übergeben müssen – für Callbacks, benutzerdefinierte Komparatoren oder Event-Handler – ohne separate Funktionsobjekte zu definieren.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Event-Handler-System bauen, das die Leistungsfähigkeit von Lambda-Ausdrücken mit verschiedenen Capture-Modi demonstriert. Sie erstellen einen einfachen Event-Dispatcher, der Callbacks speichert und aufruft, um zu zeigen, wie Lambdas den externen Zustand auf verschiedene Weise erfassen können.

Sie organisieren Ihren Code in drei Dateien:

  • EventDispatcher.h: Definieren Sie eine EventDispatcher-Klasse, die Event-Callbacks verwaltet.

    Ihr Dispatcher sollte Callbacks mithilfe eines std::vector von std::function<void()> speichern. Fügen Sie diese Methoden hinzu:

    • addCallback(std::function<void()> callback) — fügt der Liste einen Callback hinzu
    • fireAll() — ruft alle gespeicherten Callbacks in der richtigen Reihenfolge auf
    • clear() — entfernt alle Callbacks

    Sie müssen <functional> und <vector> einbinden.

  • EventDispatcher.cpp: Implementieren Sie die Methoden für Ihren Dispatcher. Die Methode fireAll() sollte einfach alle Callbacks durchlaufen und jeden einzelnen aufrufen.
  • main.cpp: Lesen Sie zwei Eingaben ein:
    1. Eine Basiszahl (Integer)
    2. Einen Multiplikator (Integer)

    Erstellen Sie einen EventDispatcher und demonstrieren Sie verschiedene Lambda-Capture-Techniken, indem Sie drei Callbacks hinzufügen:

    1. Ein Lambda, das die Basiszahl per Wert (by value) erfasst und Folgendes ausgibt: Base value: [base]
    2. Ein Lambda, das den Multiplikator per Referenz (by reference) erfasst, ihn um 1 erhöht und dann ausgibt: Multiplier after increment: [multiplier]
    3. Ein mutable Lambda, das eine Zählervariable (initialisiert mit 0) per Wert erfasst, sie bei jedem Aufruf erhöht und ausgibt: Call count: [counter]

    Nachdem Sie alle Callbacks hinzugefügt haben, rufen Sie fireAll() zweimal auf, um zu sehen, wie sich die verschiedenen Capture-Modi über mehrere Aufrufe hinweg verhalten. Geben Sie zwischen den beiden fireAll()-Aufrufen --- als Trennzeichen aus.

    Geben Sie schließlich nach beiden Runden den Endwert der Multiplikator-Variable aus main aus, um zu zeigen, wie sich das Capture per Referenz darauf ausgewirkt hat: Final multiplier: [multiplier]

Zum Beispiel mit den Eingaben 10 und 5:

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

Mit den Eingaben 42 und 0:

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

Beachten Sie die entscheidenden Verhaltensweisen: Das Capture per Wert behält die ursprüngliche Basis unverändert bei, das Capture per Referenz modifiziert die tatsächliche Multiplikator-Variable in main (akkumuliert über die Aufrufe hinweg), und der Zähler des mutable Lambdas erhöht sich bei jedem Aufruf, da fireAll() dasselbe gespeicherte Lambda-Objekt mit seiner eigenen internen Kopie aufruft, die bestehen bleibt.

Spickzettel

Lambda-Ausdrücke sind anonyme Funktionen, die inline definiert werden können. Die vollständige Syntax lautet:

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

Capture-Klauseln

Die Capture-Klausel bestimmt, auf welche externen Variablen die Lambda-Funktion zugreifen kann und wie:

int x = 10;
int y = 20;

auto byValue = [x]() { return x * 2; };        // Kopie von x
auto byRef = [&y]() { y += 5; };               // Referenz auf y
auto allByValue = [=]() { return x + y; };     // Alle Variablen kopieren
auto allByRef = [&]() { x++; y++; };           // Alle Variablen als Referenz
auto mixed = [x, &y]() { y += x; };            // Beide Modi mischen

Mutable Lambdas

Standardmäßig sind per Wert (by-value) erfasste Variablen innerhalb der Lambda-Funktion const. Verwenden Sie mutable, um Kopien zu ändern:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // Modifiziert die Kopie der Lambda-Funktion
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - Original unverändert

Init-Captures (C++14)

Erstellen Sie neue Variablen oder verschieben Sie Objekte in die Lambda-Funktion:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Besitz (Ownership) in Lambda übertragen

Verwendung von Lambdas mit std::function

Speichern Sie Lambdas in std::function für Callbacks und Event-Handler:

#include <functional>
#include <vector>

std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* x verwenden */ });
callbacks.push_back([&y]() { /* y modifizieren */ });

Probier es selbst

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

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: Füge ein Lambda hinzu, das base per Wert (BY VALUE) erfasst
    // Es soll Folgendes ausgeben: "Base value: [base]"
    
    // TODO: Füge ein Lambda hinzu, das multiplier per Referenz (BY REFERENCE) erfasst
    // Es soll multiplier um 1 erhöhen und dann ausgeben: "Multiplier after increment: [multiplier]"
    
    // TODO: Füge ein MUTABLE Lambda hinzu, das einen counter (initialisiert mit 0) per Wert erfasst
    // Es soll den counter erhöhen und ausgeben: "Call count: [counter]"
    
    // TODO: Rufe fireAll() auf, um alle Callbacks aufzurufen
    
    // TODO: Gib "---" als Trennzeichen aus
    
    // TODO: Rufe fireAll() erneut auf
    
    // TODO: Gib den finalen multiplier-Wert aus: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung