Espressioni lambda in dettaglio
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 85 di 104.
Le espressioni lambda, introdotte in C++11, sono funzioni anonime che puoi definire inline. Anche se hai già visto lambda di base con gli algoritmi STL, comprenderne la sintassi completa offre potenti possibilità per acquisire variabili e controllare il modo in cui vi si accede.
La sintassi completa di una lambda è: [capture](parameters) mutable -> return_type { body }. La clausola di cattura determina quali variabili esterne la lambda può accedere e in che modo:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // Copia de x
auto byRef = [&y]() { y += 5; }; // Referencia a y
auto allByValue = [=]() { return x + y; }; // Copia todo
auto allByRef = [&]() { x++; y++; }; // Referencia a todo
auto mixed = [x, &y]() { y += x; }; // Mezcla ambos
byRef();
std::cout << y << "\n"; // 25
}Per impostazione predefinita, le variabili acquisite per valore sono const all'interno della lambda. La parola chiave mutable consente di modificare queste copie:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Modifica la copia de la lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - el original permanece sin cambiosC++14 ha aggiunto gli init capture, che ti permettono di creare nuove variabili o spostare oggetti all'interno della lambda:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Proprietà trasferita a lambdaLe lambda sono particolarmente utili nella programmazione orientata agli oggetti quando devi passare un comportamento come parametro, ad esempio per callback, comparatori personalizzati o gestori di eventi, senza definire oggetti funzione separati.
Sfida
FacileCostruiamo un sistema di gestione degli eventi che mostri la potenza delle espressioni lambda con diverse modalità di cattura. Creerai un semplice dispatcher di eventi che memorizza e invoca callback, mostrando come le lambda possano catturare lo stato esterno in vari modi.
Organizzerai il codice in tre file:
EventDispatcher.h: definisci una classeEventDispatcherche gestisce le callback degli eventi.Il dispatcher dovrebbe memorizzare le callback usando un
std::vectordistd::function<void()>. Includi questi metodi:addCallback(std::function<void()> callback): aggiunge una callback all'elencofireAll(): invoca tutte le callback memorizzate nell'ordine in cui sono state aggiunteclear(): rimuove tutte le callback
Dovrai includere
<functional>e<vector>.EventDispatcher.cpp: implementa i metodi del dispatcher. Il metodofireAll()dovrebbe semplicemente iterare su tutte le callback e invocarle una per una.main.cpp: leggi due valori in input:- Un numero di base (intero)
- Un moltiplicatore (intero)
Crea un
EventDispatchere mostra diverse tecniche di cattura delle lambda aggiungendo tre callback:- Una lambda che cattura il numero di base per valore e stampa:
Base value: [base] - Una lambda che cattura il moltiplicatore per riferimento, lo incrementa di 1 e poi stampa:
Multiplier after increment: [multiplier] - Una lambda mutable che cattura per valore una variabile contatore (inizializzata a 0), la incrementa ogni volta che viene chiamata e stampa:
Call count: [counter]
Dopo aver aggiunto tutte le callback, chiama
fireAll()due volte per osservare il comportamento delle diverse modalità di cattura nelle chiamate successive. Tra le due chiamate afireAll(), stampa---come separatore.Infine, dopo entrambi i cicli, stampa il valore finale della variabile moltiplicatore di main per mostrare come la cattura per riferimento l'abbia modificata:
Final multiplier: [multiplier]
Per esempio, con gli input 10 e 5:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7Con gli input 42 e 0:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Nota i comportamenti principali: la cattura per valore mantiene invariato il valore originale di base, la cattura per riferimento modifica la variabile moltiplicatore effettiva in main (accumulando gli incrementi tra le chiamate) e il contatore della lambda mutable restituisce 1 in ogni ciclo. Questo perché fireAll() scorre le sue callback per valore: a ogni ciclo viene inviata una copia nuova della std::function memorizzata, il cui contatore catturato parte da 0 e viene incrementato a 1; quindi, lo stato della callback memorizzata non viene mai aggiornato.
Provalo tu
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: Aggiungi una lambda che acquisisca base PER VALORE
// Deve stampare: "Base value: [base]"
// TODO: Aggiungi una lambda che acquisisca multiplier PER RIFERIMENTO
// Deve incrementare multiplier di 1, poi stampare: "Multiplier after increment: [multiplier]"
// TODO: Aggiungi una lambda MUTABILE che acquisisca per valore un contatore (inizializzato a 0)
// Deve incrementare il contatore e stampare: "Call count: [counter]"
// TODO: Chiama fireAll() per eseguire tutti i callback
// TODO: Stampa "---" come separatore
// TODO: Chiama di nuovo fireAll()
// TODO: Stampa il valore finale di multiplier: "Final multiplier: [multiplier]"
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore generico12Funzionalità moderne di C++
Semantica di spostamento e rvaluePerfect forwardingEspressioni lambda in dettagliostd::function e std::bindconstexpr e constevalStructured bindingsoptional, variant, anyEsercitati da solo: Compilatore C++ online