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 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 lambda

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

challenge icon

Défi

Facile

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

    Votre répartiteur doit stocker les rappels en utilisant un std::vector de std::function<void()>. Incluez ces méthodes :

    • addCallback(std::function<void()> callback) — ajoute un rappel à la liste
    • fireAll() — invoque tous les rappels stockés dans l'ordre
    • clear() — supprime tous les rappels

    Vous devrez inclure <functional> et <vector>.

  • EventDispatcher.cpp : Implémentez les méthodes pour votre répartiteur. La méthode fireAll() doit simplement itérer à travers tous les rappels et invoquer chacun d'eux.
  • 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 lambda en ajoutant trois rappels :

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

Notez 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 modes

Lambdas 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 lambda

Utilisation 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;
}
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