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 modèles 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 transfert parfait résout ce problème en transmettant les arguments exactement comme ils ont été reçus, tout en conservant leur catégorie de valeur.
Les éléments essentiels sont les références de transfert (écrites sous la forme T&& dans le contexte d’un modèle) et std::forward. Une référence de transfert peut se lier à la fois aux lvalues et 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é
wrapper(20); // Appelle process(int&&) - rvalue préservé
}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 déplacement de fonctionner correctement lors de la transmission aux constructeurs ou à d’autres fonctions.
Le transfert parfait est essentiel pour écrire des fonctions fabrique, des classes enveloppes et tout code générique qui doit construire des objets ou invoquer des fonctions tout en préservant l’intention de l’appelant concernant les déplacements par rapport aux copies.
Défi
FacileConstruisons une fonction fabrique qui illustre le transfert parfait 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 transmet les arguments au constructeur d’une classe, en veillant à ce que les lvalues soient copiées et que les rvalues soient déplacées.
Vous allez organiser votre code sur trois fichiers :
Widget.h: définissez une classeWidgetqui indique comment elle reçoit ses données.Votre
Widgetdoit stocker un nomstd::stringet une valeurint. Fournissez deux constructeurs :- Un constructeur qui accepte
const std::string&(référence lvalue) etint: afficheWidget constructed (copy): [name] - Un constructeur qui accepte
std::string&&(référence rvalue) etint: afficheWidget constructed (move): [name]et déplace la chaîne
Ajoutez également une méthode
display()qui affiche[name]: [value].- Un constructeur qui accepte
Factory.h: créez votre fonction fabrique utilisant le transfert parfait.Écrivez une fonction template
make_objectqui accepte une référence de transfert pour le nom et unintordinaire pour la valeur. Utilisezstd::forwardpour transmettre l’argument name 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)
- Une valeur (entier)
Illustrez le transfert parfait en créant deux widgets :
- Tout d’abord, stockez le nom dans une variable locale et passez-le à
make_objecten tant que lvalue - Ensuite, passez directement à
"_temp"une chaîne temporaire (le même nom concaténé avecmake_object) en 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 essentielle est que votre fonction make_object ne sait pas si elle a reçu une lvalue ou une rvalue, mais qu’en utilisant std::forward, elle préserve cette information lors de l’appel du constructeur de Widget. Cela permet à la résolution de surcharge du constructeur de sélectionner la version appropriée, autorisant ainsi des déplacements efficaces pour les objets temporaires tout en copiant de manière sûre les variables nommées.
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 second widget en passant une chaîne temporaire comme une rvalue
// Passer (name + "_temp") directement à make_object
// TODO: Appeler display() sur le second 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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur générique12Fonctionnalités modernes de C++
Sémantique de déplacement et rvaluesTransfert parfaitExpressions lambda en profondeurstd::function et std::bindconstexpr et constevalLiaisons structuréesoptional, variant, anyEntraînez-vous par vous-même : Compilateur C++ en ligne