Menu
Coddy logo textTech

Wyrażenia lambda w szczegółach

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 85 z 104.

Wyrażenia lambda, wprowadzone w C++11, to funkcje anonimowe, które możesz definiować bezpośrednio w kodzie. Choć widziałeś już podstawowe lambdy używane z algorytmami STL, zrozumienie ich pełnej składni otwiera dostęp do zaawansowanych możliwości przechwytywania zmiennych i kontrolowania sposobu dostępu do nich.

Pełna składnia lambdy wygląda tak: [capture](parameters) mutable -> return_type { body }. Klauzula przechwytywania określa, do których zmiennych zewnętrznych lambda ma dostęp i w jaki sposób:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;
    
    auto byValue = [x]() { return x * 2; };        // Kopia x
    auto byRef = [&y]() { y += 5; };               // Referencja do y
    auto allByValue = [=]() { return x + y; };     // Kopiuj wszystko
    auto allByRef = [&]() { x++; y++; };           // Przechwyć wszystko przez referencję
    auto mixed = [x, &y]() { y += x; };            // Połącz oba sposoby
    
    byRef();
    std::cout << y << "\n";  // 25
}

Domyślnie zmienne przechwycone przez wartość są wewnątrz lambdy const. Słowo kluczowe mutable pozwala modyfikować te kopie:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // Modyfikuje kopię należącą do lambdy
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - oryginał pozostaje bez zmian

W C++14 dodano przechwytywanie z inicjalizacją, które pozwala tworzyć nowe zmienne lub przenosić obiekty do lambdy:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Własność przeniesiona do lambda

Wyrażenia lambda są szczególnie przydatne w programowaniu obiektowym, gdy trzeba przekazać zachowanie jako parametr — na przykład w przypadku funkcji zwrotnych, niestandardowych komparatorów lub procedur obsługi zdarzeń — bez definiowania oddzielnych obiektów funkcyjnych.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system obsługi zdarzeń, który pokazuje możliwości wyrażeń lambda z różnymi trybami przechwytywania. Utworzysz prosty dyspozytor zdarzeń, który przechowuje i wywołuje funkcje zwrotne, pokazując, jak lambdy mogą przechwytywać zewnętrzny stan na różne sposoby.

Rozmieścisz kod w trzech plikach:

  • EventDispatcher.h: Zdefiniuj klasę EventDispatcher, która zarządza funkcjami zwrotnymi zdarzeń.

    Dyspozytor powinien przechowywać funkcje zwrotne w std::vector elementów typu std::function<void()>. Uwzględnij następujące metody:

    • addCallback(std::function<void()> callback): dodaje funkcję zwrotną do listy
    • fireAll(): wywołuje po kolei wszystkie przechowywane funkcje zwrotne
    • clear(): usuwa wszystkie funkcje zwrotne

    Dołącz nagłówki <functional> i <vector>.

  • EventDispatcher.cpp: Zaimplementuj metody dyspozytora. Metoda fireAll() powinna po prostu przejść przez wszystkie funkcje zwrotne i wywołać każdą z nich.
  • main.cpp: Wczytaj dwa wejścia:
    1. Liczbę bazową (liczbę całkowitą)
    2. Mnożnik (liczbę całkowitą)

    Utwórz obiekt EventDispatcher i pokaż różne techniki przechwytywania w lambdach, dodając trzy funkcje zwrotne:

    1. Lambdę, która przechwytuje liczbę bazową przez wartość i wypisuje: Base value: [base]
    2. Lambdę, która przechwytuje mnożnik przez referencję, zwiększa go o 1, a następnie wypisuje: Multiplier after increment: [multiplier]
    3. Modyfikowalną lambdę, która przechwytuje przez wartość zmienną licznika (zainicjalizowaną wartością 0), zwiększa ją przy każdym wywołaniu i wypisuje: Call count: [counter]

    Po dodaniu wszystkich funkcji zwrotnych wywołaj fireAll() dwa razy, aby zobaczyć, jak różne tryby przechwytywania zachowują się przy wielokrotnych wywołaniach. Pomiędzy dwoma wywołaniami fireAll() wypisz --- jako separator.

    Na koniec, po obu rundach, wypisz końcową wartość zmiennej mnożnika z funkcji main, aby pokazać, jak wpłynęło na nią przechwycenie przez referencję: Final multiplier: [multiplier]

Na przykład dla wejść 10 i 5:

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

Dla wejść 42 i 0:

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

Zwróć uwagę na kluczowe zachowania: przechwycenie przez wartość zachowuje oryginalną, niezmienioną liczbę bazową, przechwycenie przez referencję modyfikuje rzeczywistą zmienną mnożnika w funkcji main (zmiana kumuluje się między wywołaniami), a licznik modyfikowalnej lambdy przyjmuje wartość 1 w każdej rundzie. Dzieje się tak dlatego, że fireAll() iteruje po funkcjach zwrotnych, przekazując je przez wartość: w każdej rundzie wywoływana jest nowa kopia przechowywanego elementu std::function, którego przechwycony licznik zaczyna od 0 i zostaje zwiększony do 1, więc stan samej przechowywanej funkcji zwrotnej nigdy nie ulega zmianie.

Spróbuj swoich sił

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

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: Dodaj lambdę, która przechwytuje base PRZEZ WARTOŚĆ
    // Powinna wypisać: "Base value: [base]"
    
    // TODO: Dodaj lambdę, która przechwytuje multiplier PRZEZ REFERENCJĘ
    // Powinna zwiększyć multiplier o 1, a następnie wypisać: "Multiplier after increment: [multiplier]"
    
    // TODO: Dodaj lambdę MUTABLE, która przechwytuje przez wartość licznik (zainicjalizowany wartością 0)
    // Powinna zwiększyć licznik i wypisać: "Call count: [counter]"
    
    // TODO: Wywołaj fireAll(), aby uruchomić wszystkie funkcje zwrotne
    
    // TODO: Wypisz "---" jako separator
    
    // TODO: Ponownie wywołaj fireAll()
    
    // TODO: Wypisz końcową wartość multiplier: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online