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 zum Erfassen von Variablen und zur Steuerung ihres Zugriffs frei.

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; };     // Alles kopieren
    auto allByRef = [&]() { x++; y++; };           // Alles als Referenz
    auto mixed = [x, &y]() { y += x; };            // Beides mischen
    
    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, mit denen Sie neue Variablen erstellen oder Objekte in das Lambda verschieben können:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Besitz 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 einen Callback zur Liste hinzu
    • fireAll() — ruft alle gespeicherten Callbacks in der entsprechenden 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 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: 1
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: 1
Final multiplier: 2

Beachten Sie die entscheidenden Verhaltensweisen: Das Capture per Wert hält die ursprüngliche Basis unverändert, das Capture per Referenz modifiziert die tatsächliche Multiplikator-Variable in main (akkumuliert über die Aufrufe hinweg), und der Zähler des mutable Lambdas zeigt in jeder Runde 1 an. Das liegt daran, dass fireAll() seine Callbacks per Wert iteriert: Jede Runde ruft eine frische Kopie der gespeicherten std::function auf, deren erfasster Zähler bei 0 beginnt und auf 1 erhöht wird, sodass der eigene Zustand des gespeicherten Callbacks nie fortgeschrieben wird.

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 ALS WERT (BY VALUE) erfasst
    // Es soll ausgeben: "Base value: [base]"
    
    // TODO: Füge ein Lambda hinzu, das multiplier ALS 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 Zähler (initialisiert auf 0) als Wert erfasst
    // Es soll den Zähler erhöhen und ausgeben: "Call count: [counter]"
    
    // TODO: Rufe fireAll() auf, um alle Callbacks auszuführen
    
    // TODO: Gib "---" als Trennzeichen aus
    
    // TODO: Rufe fireAll() erneut auf
    
    // TODO: Gib den finalen Multiplikator-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