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 dans C++11, sont des fonctions anonymes que vous pouvez définir directement. Bien que vous ayez déjà vu des lambdas de base avec les algorithmes de la STL, comprendre leur syntaxe complète permet de bénéficier de puissantes possibilités pour capturer les variables et contrôler la manière dont elles sont accessibles.
La syntaxe complète d’une lambda est la suivante : [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; }; // Copie de tous
auto allByRef = [&]() { x++; y++; }; // Référence à tous
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 de modifier 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 - original inchangéC++14 a ajouté les captures initialisées, ce qui vous permet 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;
}; // Propriété transférée dans le lambdaLes lambdas sont particulièrement utiles en POO lorsque vous devez transmettre un comportement en tant que paramètre — pour des rappels, des comparateurs personnalisés ou des gestionnaires d’événements — sans définir d’objets fonction distincts.
Défi
FacileConstruisons un système de gestion des événements qui met en valeur 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 fonctions de rappel, afin de montrer comment les lambdas peuvent capturer un état externe de différentes manières.
Vous allez organiser votre code sur trois fichiers :
EventDispatcher.h: définissez une classeEventDispatcherqui gère les fonctions de rappel d'événements.Votre répartiteur doit stocker les fonctions de rappel à l'aide d'un
std::vectordestd::function<void()>. Incluez ces méthodes :addCallback(std::function<void()> callback): ajoute une fonction de rappel à la listefireAll(): invoque toutes les fonctions de rappel stockées dans l'ordreclear(): supprime toutes les fonctions de rappel
Vous devrez inclure
<functional>et<vector>.EventDispatcher.cpp: implémentez les méthodes de votre répartiteur. La méthodefireAll()doit simplement parcourir toutes les fonctions de rappel et invoquer chacune d'elles.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 par lambda en ajoutant trois fonctions de rappel :- 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 appel et affiche :
Call count: [counter]
Après avoir ajouté toutes les fonctions de rappel, appelez
fireAll()deux fois pour observer le comportement des différents modes de capture lors de plusieurs invocations. Entre les deux appels àfireAll(), affichez---comme séparateur.Enfin, après les deux séries, affichez la valeur finale de la variable multiplicateur de main pour montrer comment la capture par référence l'a modifié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: 2Remarquez les comportements essentiels : la capture par valeur conserve le nombre de base d'origine inchangé, la capture par référence modifie la variable multiplicateur réelle dans main (en accumulant les modifications au fil des appels), et le compteur de la lambda mutable lit 1 à chaque série. Cela s'explique par le fait que fireAll() parcourt ses fonctions de rappel par valeur : chaque série invoque une nouvelle copie de la std::function stockée, dont le compteur capturé commence à 0 et est incrémenté jusqu'à 1 ; l'état propre de la fonction de rappel stockée n'avance donc jamais.
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
// It should increment multiplier by 1, then print: "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 callbacks
// TODO: Afficher "---" comme séparateur
// TODO: Appeler fireAll() à nouveau
// TODO: Print the final multiplier value: "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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur générique12Fonctionnalités modernes de C++
Sémantique de déplacement et rvaluesTransfert parfaitExpressions lambda en profondeurstd::function et std::bindconstexpr et constevalLiaisons structuréesoptional, variant, anyEntraînez-vous par vous-même : Compilateur C++ en ligne