Menu
Coddy logo textTech

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 lambda

Les 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.

challenge icon

Défi

Facile

Construisons 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 classe EventDispatcher qui gère les fonctions de rappel d'événements.

    Votre répartiteur doit stocker les fonctions de rappel à l'aide d'un std::vector de std::function<void()>. Incluez ces méthodes :

    • addCallback(std::function<void()> callback) : ajoute une fonction de rappel à la liste
    • fireAll() : invoque toutes les fonctions de rappel stockées dans l'ordre
    • clear() : 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éthode fireAll() doit simplement parcourir toutes les fonctions de rappel et invoquer chacune d'elles.
  • main.cpp : lisez deux entrées :
    1. Un nombre de base (entier)
    2. Un multiplicateur (entier)

    Créez un EventDispatcher et démontrez différentes techniques de capture par lambda en ajoutant trois fonctions de rappel :

    1. Une lambda qui capture le nombre de base par valeur et affiche : Base value: [base]
    2. Une lambda qui capture le multiplicateur par référence, l'incrémente de 1, puis affiche : Multiplier after increment: [multiplier]
    3. 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: 7

Avec 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: 2

Remarquez 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;
}
quiz iconTestez-vous

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

Entraînez-vous par vous-même : Compilateur C++ en ligne