T&& en C++11 : que signifie le double esperluette ? Analyse complète

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Que se cache-t-il derrière la double esperluette en C++11 ? Décryptons T&&

\n\n

Si vous avez déjà programmé en C++ et jeté un œil à du code écrit selon la norme C++11 ou ultérieure, vous avez sûrement rencontré une syntaxe étrange : T&&. La double esperluette saute immédiatement aux yeux — car dans le bon vieux C++98/03, nous avions l'habitude que & signifie une référence. Et là, il y en a deux ! Qu'est-ce que c'est ? Une référence à une référence ? Une erreur du compilateur ? Ou quelque chose de bien plus puissant ?

\n\n

Indice : ce n'est pas une erreur. C'est l'une des fonctionnalités les plus puissantes du C++ moderne, qui a révolutionné la perception des performances et de la gestion des ressources. Dans cet article, nous allons comprendre ce que signifie T&&, en quoi il diffère d'une référence classique, et comment l'utiliser correctement pour que votre code vole, plutôt que de ramper.

\n\n

Nous répondrons aux vraies questions de Stack Overflow qui tourmentent des milliers de développeurs, et montrerons des exemples de code concrets. C'est parti !

\n\n

1. Introduction aux références rvalue : pourquoi une seule esperluette ne suffit-elle pas ?

\n\n

Pour commencer, rappelons ce que sont lvalue et rvalue. En bref :

\n\n
    \n
  • lvalue (left value) — est un objet qui a un nom et une adresse en mémoire. Exemple : int x = 5; — x est une lvalue.
  • \n
  • rvalue (right value) — est une valeur temporaire qui n'a pas de nom et qui est généralement détruite après l'expression. Exemple : 5, x + 2, le résultat d'un appel de fonction getTemp().
  • \n
\n\n

En C++03, nous n'avions qu'une seule référence — int& (référence lvalue). Elle ne pouvait se lier qu'à une lvalue. Essayez de passer un objet temporaire à une fonction attendant int&, et le compilateur générera une erreur. C'est une protection : modifier un objet temporaire est une mauvaise idée.

\n\n

Mais que faire si nous voulons déplacer les données d'un objet temporaire, plutôt que de les copier ? Par exemple, nous avons une chaîne std::string qui est retournée par une fonction. La copier coûte cher. En revanche, "voler" son tampon interne est peu coûteux. C'est là qu'interviennent les références rvalue (T&&).

\n\n
// Fonction qui prend une référence rvalue\nvoid process(std::string&& str) {\n    std::cout << "Traitement d'une chaîne temporaire : " << str << std::endl;\n    // Ici, on peut déplacer les données, plutôt que de les copier\n}\n\nint main() {\n    std::string s = "Bonjour";\n    process(std::move(s)); // std::move transforme une lvalue en rvalue\n    // process(s); // Erreur ! s est une lvalue, mais la fonction attend une rvalue\n    return 0;\n}
\n\n

Maintenant, T&& n'est pas une référence à une référence, mais un nouveau type de référence qui ne peut se lier qu'à une rvalue. C'est la réponse à la question principale : T&& en C++11 signifie référence rvalue.

\n\n

2. Sémantique de déplacement : comment T&& accélère le code

\n\n

La principale raison de l'apparition des références rvalue est la sémantique de déplacement (move semantics). Imaginez que vous ayez un vecteur de 10 000 éléments. Dans l'ancienne version du C++, lors du retour d'une fonction, il était copié :

\n\n
std::vector<int> createBigVector() {\n    std::vector<int> v(10000, 42);\n    return v; // Copie en C++03, déplacement en C++11\n}
\n\n

En C++11, le compilateur voit que v est une lvalue, mais il est intelligent : il sait que v va bientôt mourir, donc il applique un déplacement implicite. Mais si vous voulez déplacer explicitement des données, utilisez std::move.

\n\n

Considérons une classe avec de la mémoire dynamique :

\n\n
class MyBuffer {\nprivate:\n    int* data;\n    size_t size;\npublic:\n    // Constructeur de déplacement (move constructor)\n    MyBuffer(MyBuffer&& other) noexcept\n        : data(other.data), size(other.size) {\n        other.data = nullptr; // Important ! Laisser la source dans un état valide\n        other.size = 0;\n    }\n    \n    // Constructeur classique                

Blogs

Book Recommendations