Foncteurs et expressions lambda
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 74 sur 104.
De nombreux algorithmes de la STL acceptent un objet appelable (callable) qui personnalise leur comportement. Vous avez déjà vu des lambdas utilisées avec std::transform. Explorons à la fois les foncteurs et les expressions lambda - deux façons de créer des objets appelables en C++.
Un foncteur (objet fonction) est une classe qui surcharge l'operator(), permettant aux instances d'être appelées comme des fonctions :
#include <algorithm>
#include <vector>
#include <iostream>
struct MultiplyBy {
int factor;
MultiplyBy(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
int main() {
std::vector<int> nums = {1, 2, 3, 4};
std::vector<int> result(nums.size());
std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));
// résultat : {3, 6, 9, 12}
}Les foncteurs peuvent maintenir un état entre les appels, ce que les fonctions classiques ne peuvent pas faire. Cependant, définir une classe pour des opérations simples est verbeux. Les expressions lambda offrent une alternative concise :
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });La syntaxe d'une lambda est [capture](parameters) { body }. La clause de capture spécifie les variables externes auxquelles la lambda peut accéder. Utilisez [=] pour tout capturer par valeur, [&] pour tout capturer par référence, ou listez des variables spécifiques comme [factor] ou [&factor].
Les lambdas sont particulièrement utiles pour des opérations ponctuelles avec des algorithmes tels que le tri selon des critères personnalisés :
std::vector<int> nums = {5, -2, 8, -1};
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });
// Trié par valeur absolue : {-1, -2, 5, 8}Défi
FacileConstruisons un calculateur de prix qui illustre à la fois les foncteurs et les expressions lambda pour appliquer différentes stratégies de remise aux prix des produits.
Vous organiserez votre code sur deux fichiers :
Discounts.h: Définissez vos foncteurs de remise et vos fonctions utilitaires ici.Créez un foncteur appelé
PercentageDiscountqui stocke un pourcentage de remise (sous forme d'entier). Sonoperator()doit prendre un prix de typedoubleet retourner le prix remisé. Par exemple, une remise de 20 % sur 100 $ devrait retourner 80 $.Créez un autre foncteur appelé
FixedDiscountqui stocke un montant fixe à soustraire (sous forme dedouble). Sonoperator()doit prendre un prix et retourner le prix moins le montant fixe (mais jamais en dessous de 0).Créez une fonction appelée
printPricesqui prend unconst std::vector<double>&et affiche tous les prix séparés par des espaces, suivis d'un saut de ligne. Formatez chaque prix avec deux décimales.main.cpp: Lisez cinq entrées (chacune sur une ligne séparée) :- Prix du premier produit (double)
- Prix du deuxième produit (double)
- Prix du troisième produit (double)
- Pourcentage de remise à appliquer (entier, ex: 20 pour 20 %)
- Montant de la remise fixe (double)
Créez un vecteur avec les trois prix et démontrez les deux approches :
- Affichez
Original prices:suivi des prix - Utilisez
std::transformavec votre foncteurPercentageDiscountpour créer un nouveau vecteur de prix remisés. AffichezAfter percentage discount:suivi des résultats - Utilisez
std::transformavec votre foncteurFixedDiscountsur les prix originaux pour créer un autre vecteur. AffichezAfter fixed discount:suivi des résultats - Utilisez
std::transformavec une expression lambda qui double chaque prix original. AffichezPremium prices (doubled):suivi des résultats - Utilisez
std::sortavec une lambda pour trier les prix originaux par ordre décroissant. AffichezSorted (high to low):suivi des prix triés
Par exemple, avec les entrées 100.00, 50.00, 75.00, 20, et 15.00 :
Original prices: 100.00 50.00 75.00
After percentage discount: 80.00 40.00 60.00
After fixed discount: 85.00 35.00 60.00
Premium prices (doubled): 200.00 100.00 150.00
Sorted (high to low): 100.00 75.00 50.00 Ce défi vous permet de comparer les foncteurs (qui maintiennent un état comme le montant de la remise) avec les lambdas (qui capturent des variables pour des opérations rapides en ligne). Les deux approches fonctionnent parfaitement avec les algorithmes de la STL comme std::transform et std::sort.
Aide-mémoire
Les algorithmes de la STL acceptent des objets appelables pour personnaliser leur comportement. Deux approches courantes sont les foncteurs (functors) et les expressions lambda.
Un foncteur est une classe qui surcharge l'operator(), permettant aux instances d'être appelées comme des fonctions :
struct MultiplyBy {
int factor;
MultiplyBy(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));Les foncteurs peuvent maintenir un état entre les appels, mais nécessitent des définitions de classe verbeuses.
Les expressions lambda offrent une alternative concise avec la syntaxe [capture](parameters) { body } :
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });La clause de capture spécifie quelles variables externes la lambda peut accéder :
[=]- capturer tout par valeur[&]- capturer tout par référence[factor]- capturer une variable spécifique par valeur[&factor]- capturer une variable spécifique par référence
Les lambdas fonctionnent bien avec des algorithmes comme std::sort pour des critères de tri personnalisés :
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });Essayez vous-même
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Lire les entrées
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Créer un vector avec les trois prix
std::vector<double> prices = {price1, price2, price3};
// TODO: Afficher "Original prices:" suivi des prix en utilisant printPrices
// TODO: Utiliser std::transform avec le foncteur PercentageDiscount
// Créer un nouveau vector pour les résultats
// Afficher "After percentage discount:" suivi des résultats
// TODO: Utiliser std::transform avec le foncteur FixedDiscount sur les prix originaux
// Créer un nouveau vector pour les résultats
// Afficher "After fixed discount:" suivi des résultats
// TODO: Utiliser std::transform avec une lambda qui double chaque prix original
// Créer un nouveau vector pour les résultats
// Afficher "Premium prices (doubled):" suivi des résultats
// TODO: Utiliser std::sort avec une lambda pour trier les prix originaux par ordre décroissant
// Afficher "Sorted (high to low):" suivi des prix triés
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érique