Transfert parfait
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 84 sur 104.
Lors de l'écriture de fonctions templates qui acceptent des arguments et les transmettent à d'autres fonctions, un problème se pose : comment préserver le fait que l'argument d'origine était une lvalue ou une rvalue ? Le perfect forwarding résout ce problème en transmettant les arguments exactement tels qu'ils ont été reçus, tout en conservant leur catégorie de valeur.
Les ingrédients clés sont les références de transfert (écrites sous la forme T&& dans un contexte de template) et std::forward. Une référence de transfert peut se lier aussi bien aux lvalues qu'aux rvalues, et std::forward convertit conditionnellement l'argument vers son type d'origine :
#include <iostream>
#include <utility>
void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int n = 10;
wrapper(n); // Appelle process(int&) - lvalue préservée
wrapper(20); // Appelle process(int&&) - rvalue préservée
}Sans std::forward, les paramètres nommés sont toujours des lvalues à l'intérieur de la fonction, même si une rvalue a été passée. Cela empêcherait la sémantique de mouvement de fonctionner correctement lors du transfert vers des constructeurs ou d'autres fonctions.
Le transfert parfait est essentiel pour écrire des fonctions d'usine, des classes wrappers et tout code générique qui doit construire des objets ou appeler des fonctions tout en préservant l'intention de l'appelant concernant les déplacements par rapport aux copies.
Défi
FacileConstruisons une fonction d'usine (factory function) qui démontre le transfert parfait (perfect forwarding) en construisant des objets tout en préservant la catégorie de valeur des arguments qui lui sont passés. Vous allez créer une fonction générique make_object qui transfère les arguments à un constructeur de classe, garantissant que les lvalues sont copiées et les rvalues sont déplacées.
Vous organiserez votre code sur trois fichiers :
Widget.h: Définissez une classeWidgetqui suit la manière dont elle reçoit ses données.Votre
Widgetdoit stocker un nom de typestd::stringet une valeur de typeint. Fournissez deux constructeurs :- Un qui prend
const std::string&(référence lvalue) etint— afficheWidget constructed (copy): [name] - Un qui prend
std::string&&(référence rvalue) etint— afficheWidget constructed (move): [name]et déplace la chaîne de caractères
Ajoutez également une méthode
display()qui affiche[name]: [value].- Un qui prend
Factory.h: Créez votre fonction d'usine à transfert parfait.Écrivez une fonction template
make_objectqui accepte une référence de transfert (forwarding reference) pour le nom et unintclassique pour la valeur. Utilisezstd::forwardpour passer l'argument du nom au constructeur deWidgettout en préservant sa catégorie de valeur d'origine.La fonction doit retourner le
Widgetconstruit par valeur.main.cpp: Lisez deux entrées :- Un nom (chaîne de caractères)
- Une valeur (entier)
Démontrez le transfert parfait en créant deux widgets :
- Premièrement, stockez le nom dans une variable locale et passez-la à
make_objecten tant que lvalue - Deuxièmement, passez une chaîne temporaire (le même nom concaténé avec
"_temp") directement àmake_objecten tant que rvalue
Après avoir créé chaque widget, appelez sa méthode
display().
Par exemple, avec les entrées Gadget et 42 :
Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42Avec les entrées Device et 100 :
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100L'idée clé est que votre fonction make_object ne sait pas si elle a reçu une lvalue ou une rvalue — mais en utilisant std::forward, elle préserve cette information lors de l'appel au constructeur de Widget. Cela permet à la résolution de surcharge du constructeur de sélectionner la version appropriée, permettant des déplacements efficaces pour les temporaires tout en copiant en toute sécurité les variables nommées.
Aide-mémoire
Le transfert parfait (perfect forwarding) permet aux fonctions templates de passer des arguments à d'autres fonctions tout en préservant leur catégorie de valeur d'origine (lvalue ou rvalue).
Le mécanisme utilise des références de transfert (T&& dans un contexte de template) combinées avec std::forward :
#include <utility>
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}Une référence de transfert peut se lier à la fois aux lvalues et aux rvalues. std::forward<T> convertit conditionnellement l'argument vers son type d'origine.
Sans std::forward, les paramètres nommés à l'intérieur d'une fonction sont toujours traités comme des lvalues, même si une rvalue a été passée. Cela empêcherait la sémantique de mouvement de fonctionner correctement.
Exemple illustrant la préservation de la catégorie de valeur :
void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int n = 10;
wrapper(n); // Appelle process(int&) - lvalue préservée
wrapper(20); // Appelle process(int&&) - rvalue préservéeLe transfert parfait est essentiel pour les fonctions de fabrique, les classes d'enveloppement (wrappers) et le code générique qui construit des objets ou appelle des fonctions tout en respectant l'intention de l'appelant concernant les déplacements ou les copies.
Essayez vous-même
#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"
using namespace std;
int main() {
// Lire l'entrée
string name;
int value;
cin >> name >> value;
// TODO: Créer le premier widget en passant name comme une lvalue
// Stocker le name dans une variable locale et le passer à make_object
// TODO: Appeler display() sur le premier widget
// TODO: Créer le deuxième widget en passant une chaîne temporaire comme une rvalue
// Passer (name + "_temp") directement à make_object
// TODO: Appeler display() sur le deuxième widget
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