Expressions Lambda en profondeur
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 85 sur 104.
Les expressions lambda, introduites en C++11, sont des fonctions anonymes que vous pouvez définir en ligne. Bien que vous ayez vu des lambdas de base avec les algorithmes de la STL, comprendre leur syntaxe complète débloque des capacités puissantes pour capturer des variables et contrôler la manière dont elles sont accédées.
La syntaxe complète d'une lambda est : [capture](parameters) mutable -> return_type { body }. La clause de capture détermine quelles variables externes la lambda peut accéder et de quelle manière :
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // Copie de x
auto byRef = [&y]() { y += 5; }; // Référence à y
auto allByValue = [=]() { return x + y; }; // Tout copier
auto allByRef = [&]() { x++; y++; }; // Tout référencer
auto mixed = [x, &y]() { y += x; }; // Mélange des deux
byRef();
std::cout << y << "\n"; // 25
}Par défaut, les variables capturées par valeur sont const à l'intérieur de la lambda. Le mot-clé mutable permet la modification de ces copies :
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Modifie la copie de la lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - l'original reste inchangéLe C++14 a ajouté les captures d'initialisation (init captures), vous permettant de créer de nouvelles variables ou de déplacer des objets dans la lambda :
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Transfert de propriété dans la lambdaLes lambdas sont particulièrement utiles en POO lorsque vous devez passer un comportement en tant que paramètre — pour des callbacks, des comparateurs personnalisés ou des gestionnaires d'événements — sans définir d'objets de fonction distincts.
Défi
FacileConstruisons un système de gestion d'événements qui met en évidence la puissance des expressions lambda avec différents modes de capture. Vous allez créer un répartiteur d'événements simple qui stocke et invoque des rappels (callbacks), démontrant comment les lambdas peuvent capturer l'état externe de diverses manières.
Vous organiserez votre code sur trois fichiers :
EventDispatcher.h: Définissez une classeEventDispatcherqui gère les rappels d'événements.Votre répartiteur doit stocker les rappels en utilisant un
std::vectordestd::function<void()>. Incluez ces méthodes :addCallback(std::function<void()> callback)— ajoute un rappel à la listefireAll()— invoque tous les rappels stockés dans l'ordreclear()— supprime tous les rappels
Vous devrez inclure
<functional>et<vector>.EventDispatcher.cpp: Implémentez les méthodes pour votre répartiteur. La méthodefireAll()doit simplement itérer à travers tous les rappels et invoquer chacun d'eux.main.cpp: Lisez deux entrées :- Un nombre de base (entier)
- Un multiplicateur (entier)
Créez un
EventDispatcheret démontrez différentes techniques de capture lambda en ajoutant trois rappels :- Une lambda qui capture le nombre de base par valeur et affiche :
Base value: [base] - Une lambda qui capture le multiplicateur par référence, l'incrémente de 1, puis affiche :
Multiplier after increment: [multiplier] - Une lambda mutable qui capture une variable compteur (initialisée à 0) par valeur, l'incrémente à chaque fois qu'elle est appelée, et affiche :
Call count: [counter]
Après avoir ajouté tous les rappels, appelez
fireAll()deux fois pour voir comment les différents modes de capture se comportent lors d'invocations multiples. Entre les deux appels àfireAll(), affichez---comme séparateur.Enfin, après les deux cycles, affichez la valeur finale de la variable multiplicateur depuis la fonction main pour montrer comment la capture par référence l'a affectée :
Final multiplier: [multiplier]
Par exemple, avec les entrées 10 et 5 :
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7Avec les entrées 42 et 0 :
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Notez les comportements clés : la capture par valeur garde la base d'origine inchangée, la capture par référence modifie la variable multiplicateur réelle dans main (s'accumulant au fil des appels), et le compteur de la lambda mutable se réinitialise à 1 à chaque cycle parce que chaque fireAll() invoque le même objet lambda stocké avec sa propre copie interne qui persiste — mais comme nous stockons des copies de std::function, observez comment l'état mutable se comporte dans votre implémentation.
Aide-mémoire
Les expressions lambda sont des fonctions anonymes qui peuvent être définies en ligne. La syntaxe complète est :
[capture](parameters) mutable -> return_type { body }Clauses de capture
La clause de capture détermine quelles variables extérieures la lambda peut accéder et comment :
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // Copie de x
auto byRef = [&y]() { y += 5; }; // Référence à y
auto allByValue = [=]() { return x + y; }; // Copier toutes les variables
auto allByRef = [&]() { x++; y++; }; // Référencer toutes les variables
auto mixed = [x, &y]() { y += x; }; // Mélanger les deux modesLambdas mutables
Par défaut, les variables capturées par valeur sont const à l'intérieur de la lambda. Utilisez mutable pour modifier les copies :
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Modifie la copie de la lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - original inchangéCaptures d'initialisation (C++14)
Créez de nouvelles variables ou déplacez des objets dans la lambda :
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Transfert de propriété dans la lambdaUtilisation des lambdas avec std::function
Stockez les lambdas dans std::function pour les rappels (callbacks) et les gestionnaires d'événements :
#include <functional>
#include <vector>
std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* utiliser x */ });
callbacks.push_back([&y]() { /* modifier y */ });Essayez vous-même
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: Ajouter une lambda qui capture base PAR VALEUR
// Elle doit afficher : "Base value: [base]"
// TODO: Ajouter une lambda qui capture multiplier PAR RÉFÉRENCE
// Elle doit incrémenter multiplier de 1, puis afficher : "Multiplier after increment: [multiplier]"
// TODO: Ajouter une lambda MUTABLE qui capture un compteur (initialisé à 0) par valeur
// Elle doit incrémenter le compteur et afficher : "Call count: [counter]"
// TODO: Appeler fireAll() pour invoquer tous les rappels (callbacks)
// TODO: Afficher "---" comme séparateur
// TODO: Appeler fireAll() à nouveau
// TODO: Afficher la valeur finale de multiplier : "Final multiplier: [multiplier]"
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique12Fonctionnalités du C++ moderne
Sémantique de mouvement et RvaluesTransfert parfaitExpressions Lambda en profondeurstd::function et std::bindconstexpr et constevalLiaisons structuréesoptional, variant, any