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 mit C++11 eingeführt wurden, sind anonyme Funktionen, die du inline definieren kannst. Du hast bereits grundlegende Lambdas mit STL-Algorithmen gesehen; wenn du ihre vollständige Syntax verstehst, eröffnen sich dir leistungsstarke Möglichkeiten, 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 Variablen außerhalb des Lambdas es 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 kopieren
    auto allByRef = [&]() { x++; y++; };           // Alle referenzieren
    auto mixed = [x, &y]() { y += x; };            // Beides mischen
    
    byRef();
    std::cout << y << "\n";  // 25
}

Standardmäßig sind per Wert erfasste Variablen innerhalb der Lambda-Funktion const. Das Schlüsselwort mutable ermöglicht die Änderung 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 hat Init-Captures hinzugefügt, mit denen du neue Variablen erstellen oder Objekte in die Lambda-Funktion verschieben kannst:

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

Lambdas sind in der OOP besonders nützlich, wenn du Verhalten als Parameter übergeben musst – etwa für Rückruffunktionen, benutzerdefinierte Vergleichsfunktionen oder Ereignishandler –, ohne separate Funktionsobjekte zu definieren.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Event-Handler-System, das die Leistungsfähigkeit von Lambda-Ausdrücken mit verschiedenen Erfassungsmodi demonstriert. Du erstellst einen einfachen Event-Dispatcher, der callbacks speichert und aufruft, und zeigst dabei, wie Lambdas externen Zustand auf verschiedene Arten erfassen können.

Du organisierst deinen Code auf drei Dateien:

  • EventDispatcher.h: Definiere eine EventDispatcher-Klasse, die Event-callbacks verwaltet.

    Dein Dispatcher sollte callbacks mithilfe eines std::vector aus std::function<void()> speichern. Füge diese Methoden ein:

    • addCallback(std::function<void()> callback): fügt der list einen callback hinzu
    • fireAll(): ruft alle gespeicherten callbacks der Reihe nach auf
    • clear(): entfernt alle callbacks

    Du musst <functional> und <vector> einbinden.

  • EventDispatcher.cpp: Implementiere die Methoden für deinen Dispatcher. Die Methode fireAll() sollte einfach durch alle callbacks iterieren und jeden einzelnen aufrufen.
  • main.cpp: Lies zwei Eingaben ein:
    1. Eine Basiszahl (Ganzzahl)
    2. Einen multiplier (Ganzzahl)

    Erstelle einen EventDispatcher und demonstriere verschiedene Lambda-Erfassungstechniken, indem du drei callbacks hinzufügst:

    1. Ein Lambda, das die Basiszahl per Wert erfasst und ausgibt: Base value: [base]
    2. Ein Lambda, das den multiplier per Referenz erfasst, ihn um 1 erhöht und anschließend ausgibt: Multiplier after increment: [multiplier]
    3. Ein mutable Lambda, das eine counter-Variable (mit dem Anfangswert 0) per Wert erfasst, sie bei jedem Aufruf erhöht und ausgibt: Call count: [counter]

    Rufe nach dem Hinzufügen aller callbacks fireAll() zweimal auf, um zu sehen, wie sich die verschiedenen Erfassungsmodi bei mehreren Aufrufen verhalten. Gib zwischen den beiden fireAll()-Aufrufen --- als Trennzeichen aus.

    Gib schließlich nach beiden Durchläufen den endgültigen Wert der multiplier-Variable aus main aus, um zu zeigen, wie sich die Erfassung per Referenz auf sie ausgewirkt hat: Final multiplier: [multiplier]

Beispiel: Bei 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

Bei 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

Beachte die wichtigsten Verhaltensweisen: Die Erfassung per Wert lässt die ursprüngliche Basis unverändert, die Erfassung per Referenz verändert die tatsächliche multiplier-Variable in main (und sammelt die Änderungen über mehrere Aufrufe hinweg), und der counter des mutable Lambdas liest in jedem Durchlauf 1. Das liegt daran, dass fireAll() seine callbacks per Wert durchläuft: In jedem Durchlauf wird eine neue Kopie der gespeicherten std::function aufgerufen, deren erfasster counter bei 0 beginnt und auf 1 erhöht wird. Daher wird der eigene Zustand des gespeicherten callbacks nie weiterentwickelt.

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 BY VALUE erfasst
    // Es sollte ausgeben: "Base value: [base]"
    
    // TODO: Füge ein Lambda hinzu, das multiplier BY REFERENCE erfasst
    // It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
    
    // TODO: Füge ein MUTABLE Lambda hinzu, das einen counter (initialisiert mit 0) per Wert erfasst
    // Es sollte 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: Print the final multiplier value: "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

Übe selbstständig: Online-C++-Compiler