std::function et std::bind
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 86 sur 104.
Bien que les lambdas soient puissantes, vous avez parfois besoin de stocker des objets appelables de différents types de manière uniforme, ou d'adapter des fonctions existantes pour qu'elles correspondent à une signature requise. std::function et std::bind de l'en-tête <functional> résolvent ces problèmes.
std::function est un wrapper à effacement de type qui peut contenir n'importe quel élément appelable (callable) correspondant à une signature spécifique - fonctions, lambdas ou objets fonctions :
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // Fonction classique
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // Lambda
std::cout << operation(3, 4) << "\n"; // 12
}std::bind crée un nouvel objet appelable en fixant certains arguments d'une fonction existante. Utilisez std::placeholders::_1, _2, etc. pour marquer les arguments qui restent variables :
#include <iostream>
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
int main() {
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1);
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Hi, Bob!
}Ces outils sont particulièrement utiles en POO pour stocker des callbacks en tant que membres de classe ou pour implémenter le patron Strategy. Cependant, le C++ moderne préfère souvent les lambdas à std::bind pour une meilleure lisibilité et de meilleures performances - utilisez std::bind principalement lorsque vous devez réorganiser les arguments ou travailler avec du code existant.
Défi
FacileConstruisons une calculatrice configurable qui démontre la puissance de std::function et std::bind. Vous allez créer un système où les opérations mathématiques peuvent être stockées, échangées et personnalisées au moment de l'exécution, montrant ainsi comment ces outils permettent une gestion flexible des rappels (callbacks).
Vous organiserez votre code sur trois fichiers :
MathOperations.h: Définissez une collection de fonctions mathématiques autonomes qui serviront de bibliothèque d'opérations.Créez ces fonctions :
add(int a, int b)— retourne la sommesubtract(int a, int b)— retourne la différence (a - b)multiply(int a, int b)— retourne le produitpower(int base, int exponent, int multiplier)— retournemultiplier * (base ^ exponent). Utilisez une boucle simple pour calculer la puissance (supposez des exposants non négatifs).
Calculator.h: Définissez une classeCalculatorqui utilisestd::functionpour stocker et exécuter des opérations de manière dynamique.Votre calculatrice doit avoir :
- Un membre privé
std::function<int(int, int)>pour stocker l'opération binaire actuelle setOperation(std::function<int(int, int)> op)— définit l'opération actuellecalculate(int a, int b)— exécute l'opération stockée et retourne le résultat
Incluez les en-têtes nécessaires (
<functional>) et assurez-vous d'inclure votre en-tête MathOperations.- Un membre privé
main.cpp: Lisez trois entrées :- Nom de l'opération :
add,subtract,multiply, ousquare - Premier nombre (entier)
- Deuxième nombre (entier)
Créez un
Calculatoret configurez-le en fonction du nom de l'opération :- Pour
add,subtract, etmultiply: assignez la fonction correspondante directement à la calculatrice - Pour
square: utilisezstd::bindpour créer une version spécialisée depowerqui utilise toujours l'exposant 2 et le multiplicateur 1. La fonction liée doit accepter deux arguments où seul le premier est utilisé comme base (le second argument peut être ignoré à l'aide d'un espace réservé/placeholder).
Après avoir défini l'opération, appelez
calculate()avec vos deux nombres et affichez :Result: [value]Démontrez ensuite l'échange d'opérations en assignant une lambda qui retourne
a + b + 100à la calculatrice, en l'exécutant avec les mêmes entrées, et en affichant :With bonus: [value]- Nom de l'opération :
Par exemple, avec les entrées add, 10, et 5 :
Result: 15
With bonus: 115Avec les entrées multiply, 7, et 3 :
Result: 21
With bonus: 110Avec les entrées square, 4, et 0 :
Result: 16
With bonus: 104Ce défi montre comment std::function offre un moyen uniforme de stocker différents types d'appelables (fonctions régulières, fonctions liées et lambdas), tandis que std::bind vous permet d'adapter des fonctions existantes en fixant certains de leurs arguments.
Aide-mémoire
L'en-tête <functional> fournit std::function et std::bind pour travailler avec des objets appelables de manière flexible.
std::function est un wrapper à effacement de type (type-erased) qui peut stocker n'importe quel appelable correspondant à une signature spécifique :
#include <functional>
std::function<int(int, int)> operation;
operation = add; // Fonction régulière
operation = [](int a, int b) { return a * b; }; // Lambdastd::bind crée un nouvel appelable en fixant certains arguments d'une fonction existante. Utilisez std::placeholders::_1, _2, etc. pour les arguments variables :
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1); // Fixe le premier argument
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1); // Réordonne les arguments
swapped("Bob", "Hi"); // Hi, Bob!Ces outils sont utiles pour stocker des rappels (callbacks) en tant que membres de classe ou pour implémenter des systèmes de rappel flexibles. Le C++ moderne préfère souvent les lambdas à std::bind pour une meilleure lisibilité.
Essayez vous-même
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// Lire les entrées
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Créer une instance de Calculator
Calculator calc;
// TODO: Configurer le calculateur en fonction du nom de l'opération
// Pour "add", "subtract", "multiply" : assigner directement la fonction correspondante
// Pour "square" : utiliser std::bind pour créer une version spécialisée de power
// qui utilise toujours l'exposant 2 et le multiplicateur 1
// Astuce : Utiliser std::placeholders::_1 pour l'argument de base
if (operation == "add") {
// Votre code ici
} else if (operation == "subtract") {
// Votre code ici
} else if (operation == "multiply") {
// Votre code ici
} else if (operation == "square") {
// Votre code ici - utiliser std::bind avec la fonction power
}
// TODO: Appeler calculate() et afficher le résultat
// Format : "Result: [value]"
// TODO: Démontrer l'échange d'opérations
// Assigner une lambda qui retourne a + b + 100 au calculateur
// Appeler calculate() à nouveau et afficher le résultat
// Format : "With bonus: [value]"
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