Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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 classe Widget qui indique comment elle reçoit ses données.

    Votre Widget doit stocker un nom std::string et une valeur int. Fournissez deux constructeurs :

    • Un constructeur qui accepte const std::string& (référence lvalue) et int : affiche Widget constructed (copy): [name]
    • Un constructeur qui accepte std::string&& (référence rvalue) et int : affiche Widget constructed (move): [name] et déplace la chaîne

    Ajoutez également une méthode display() qui affiche [name]: [value].

  • Factory.h : créez votre fonction fabrique utilisant le transfert parfait.

    Écrivez une fonction template make_object qui accepte une référence de transfert pour le nom et un int ordinaire pour la valeur. Utilisez std::forward pour transmettre l’argument name au constructeur de Widget tout en préservant sa catégorie de valeur d’origine.

    La fonction doit retourner le Widget construit par valeur.

  • main.cpp : lisez deux entrées :
    1. Un nom (chaîne)
    2. Une valeur (entier)

    Illustrez le transfert parfait en créant deux widgets :

    1. Tout d’abord, stockez le nom dans une variable locale et passez-le à make_object en tant que lvalue
    2. Ensuite, passez directement à "_temp" une chaîne temporaire (le même nom concaténé avec make_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: 42

Avec les entrées Device et 100 :

Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100

L’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;
}
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

Entraînez-vous par vous-même : Compilateur C++ en ligne