Déclarateur de référence Rvalue : &&

Contient une référence à une expression rvalue.

Syntaxe

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqoptptr-abstract-declaratoropt

Notes

Les références rvalue vous permettent de différencier une lvalue d'une rvalue. Les références Lvalue et les références rvalue sont syntactiques et sémantiquement similaires, mais elles suivent des règles légèrement différentes. Pour plus d’informations sur lvalues et rvalues, consultez Lvalues et Rvalues. Pour plus d’informations sur les références lvalue, consultez Lvalue Reference Declarator : &.

Les sections suivantes décrivent comment les références rvalue prennent en charge l’implémentation de la sémantique de déplacement et le transfert parfait.

Sémantique de mouvement

Les références Rvalue prennent en charge l’implémentation de la sémantique de déplacement, ce qui peut augmenter considérablement les performances de vos applications. La sémantique de déplacement vous permet d'écrire du code qui transfère des ressources (telles que la mémoire allouée de manière dynamique) d'un objet vers un autre. La sémantique de déplacement fonctionne car elle permet le transfert de ressources à partir d’objets temporaires : celles qui ne peuvent pas être référencées ailleurs dans le programme.

Pour implémenter la sémantique de déplacement, vous fournissez généralement un constructeur de déplacement et éventuellement un opérateur d’affectation de déplacement (operator=), à votre classe. Les opérations de copie et d'assignation dont les sources sont des rvalues profitent ensuite automatiquement de la sémantique de déplacement. Contrairement au constructeur de copie par défaut, le compilateur ne fournit pas de constructeur de déplacement par défaut. Pour plus d’informations sur l’écriture et l’utilisation d’un constructeur de déplacement, consultez Les constructeurs move et les opérateurs d’affectation de déplacement.

Vous pouvez aussi surcharger des fonctions et des opérateurs ordinaires pour tirer parti de la sémantique de déplacement. Visual Studio 2010 introduit la sémantique de déplacement dans la bibliothèque C++ Standard. Par exemple, la string classe implémente des opérations qui utilisent la sémantique de déplacement. Prenons l'exemple suivant qui concatène plusieurs chaînes et affiche le résultat :

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Avant Visual Studio 2010, chaque appel à operator+ allouer et retourne un nouvel objet temporaire string (une rvalue). operator+ ne peut pas ajouter une chaîne à l’autre, car elle ne sait pas si les chaînes sources sont des valeurs lvalues ou rvalues. Si les chaînes sources sont à la fois des valeurs lvalues, elles peuvent être référencées ailleurs dans le programme et ne doivent donc pas être modifiées. Vous pouvez modifier operator+ pour prendre des valeurs rvalues à l’aide de références rvalue, qui ne peuvent pas être référencées ailleurs dans le programme. Avec cette modification, operator+ vous pouvez maintenant ajouter une chaîne à une autre. La modification réduit considérablement le nombre d’allocations de mémoire dynamiques que la string classe doit effectuer. Pour plus d’informations sur la string classe, consultez basic_string Classe.

La sémantique de déplacement permet également lorsque le compilateur ne peut pas utiliser l’optimisation de la valeur de retour (RVO) ou l’optimisation de la valeur de retour nommée (NRVO). Dans ces cas-là, le compilateur appelle le constructeur de déplacement si le type le définit.

Pour mieux comprendre la sémantique de déplacement, prenez comme exemple l'insertion d'un élément dans un objet vector. Si la capacité de l’objet vector est dépassée, l’objet vector doit réallouer suffisamment de mémoire pour ses éléments, puis copier chaque élément vers un autre emplacement de mémoire pour rendre place à l’élément inséré. Lorsqu’une opération d’insertion copie un élément, elle crée d’abord un élément. Ensuite, il appelle le constructeur de copie pour copier les données de l’élément précédent vers le nouvel élément. Enfin, il détruit l’élément précédent. La sémantique de déplacement vous permet de déplacer directement des objets sans avoir à effectuer des opérations coûteuses d’allocation de mémoire et de copie.

Pour tirer parti de la sémantique de déplacement dans l'exemple vector, vous pouvez écrire un constructeur de déplacement pour déplacer des données d'un objet vers un autre.

Pour plus d’informations sur l’introduction de la sémantique de déplacement dans la bibliothèque C++ Standard dans Visual Studio 2010, consultez bibliothèque C++ Standard.

Transfert parfait

Le transfert parfait réduit le besoin en fonctions surchargées et permet d'éviter le problème de transfert. Le problème de transfert peut se produire lorsque vous écrivez une fonction générique qui prend des références en tant que paramètres. Si elle transmet (ou transfère) ces paramètres à une autre fonction, par exemple si elle prend un paramètre de type const T&, la fonction appelée ne peut pas modifier la valeur de ce paramètre. Si la fonction générique prend un paramètre de type T&, la fonction ne peut pas être appelée à l’aide d’une valeur rvalue (par exemple, un objet temporaire ou un littéral entier).

Normalement, pour résoudre ce problème, vous devez fournir des versions surchargées de la fonction générique qui acceptent T& et const T& pour chacun de ses paramètres. Par conséquent, le nombre de fonctions surchargées augmente de façon exponentielle avec le nombre de paramètres. Les références Rvalue vous permettent d’écrire une version d’une fonction qui accepte des arguments arbitraires. Ensuite, cette fonction peut les transférer vers une autre fonction comme si l’autre fonction avait été appelée directement.

Prenez l'exemple suivant qui déclare quatre types, W, X, Y et Z. Le constructeur pour chaque type prend une combinaison différente de const références nonconst lvalue en tant que paramètres.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

Supposez que vous souhaitez écrire une fonction générique qui génère des objets. L'exemple suivant montre une manière d'écrire cette fonction :

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

L'exemple suivant montre un appel valide à la fonction factory :

int a = 4, b = 5;
W* pw = factory<W>(a, b);

Toutefois, l’exemple suivant ne contient pas d’appel valide à la factory fonction. C’est parce que factory prend des références lvalue modifiables en tant que paramètres, mais elle est appelée à l’aide de rvalues :

Z* pz = factory<Z>(2, 2);

Normalement, pour résoudre ce problème, vous devez créer une version surchargée de la fonction factory pour chaque combinaison de paramètres A& et const A&. Les références rvalue vous permettent d'écrire une version de la fonction factory, comme indiqué dans l'exemple suivant :

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

Cet exemple utilise des références rvalue comme paramètres de la fonction factory. L’objectif de la std::forward fonction est de transférer les paramètres de la fonction de fabrique au constructeur de la classe de modèle.

L'exemple suivant illustre la fonction main qui utilise la fonction modifiée factory pour créer des instances des classes W, X, Y et Z. La fonction factory modifiée transfère ses paramètres (lvalues ou rvalues) au constructeur de classe approprié.

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

Propriétés des références rvalue

Vous pouvez surcharger une fonction pour prendre une référence lvalue et une référence rvalue.

En surchargeant une fonction pour prendre une const référence lvalue ou une référence rvalue, vous pouvez écrire du code qui distingue les objets non modifiables (lvalues) et les valeurs temporaires modifiables (rvalues). Vous pouvez passer un objet à une fonction qui accepte une référence rvalue, sauf si l’objet est marqué comme const. L'exemple suivant illustre la fonction f, qui est surchargée pour accepter une référence lvalue et une référence rvalue. La fonction main appelle f avec des lvalues et une rvalue.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
   cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
   MemoryBlock block;
   f(block);
   f(MemoryBlock());
}

Cet exemple produit la sortie suivante :

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

Dans cet exemple, le premier appel à f passe une variable locale (une lvalue) comme argument. Le deuxième appel à f passe un objet temporaire comme argument. Étant donné que l’objet temporaire ne peut pas être référencé ailleurs dans le programme, l’appel est lié à la version surchargée de f ce qui prend une référence rvalue, qui est libre de modifier l’objet.

Le compilateur traite une référence rvalue nommée comme une valeur lvalue et une référence rvalue non nommée comme une valeur rvalue.

Les fonctions qui prennent une référence rvalue comme paramètre traitent le paramètre comme une valeur lvalue dans le corps de la fonction. Le compilateur traite une référence rvalue nommée comme une valeur lvalue. C’est parce qu’un objet nommé peut être référencé par plusieurs parties d’un programme. Il est dangereux de permettre à plusieurs parties d’un programme de modifier ou de supprimer des ressources de cet objet. Par exemple, si plusieurs parties d’un programme essaient de transférer des ressources à partir du même objet, seul le premier transfert réussit.

L'exemple suivant illustre la fonction g, qui est surchargée pour accepter une référence lvalue et une référence rvalue. La fonction f accepte une référence rvalue comme paramètre (une référence rvalue nommée) et retourne une référence rvalue (une référence rvalue sans nom). Dans l'appel à g à partir de f, la résolution de surcharge sélectionne la version de g qui accepte une référence lvalue, car le corps de f traite son paramètre comme une lvalue. Dans l’appel à g à partir de main, la résolution de surcharge sélectionne la version de g qui accepte une référence rvalue car f retourne une référence rvalue.

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
   g(block);
   return move(block);
}

int main()
{
   g(f(MemoryBlock()));
}

Cet exemple produit la sortie suivante :

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Dans l’exemple, la main fonction transmet une valeur rvalue à f. Le corps de f traite son paramètre nommé comme une lvalue. L'appel de f à g lie le paramètre à une référence lvalue (première version surchargée de g).

  • Vous pouvez convertir une lvalue en référence rvalue.

La fonction bibliothèque std::move standard C++ vous permet de convertir un objet en référence rvalue à cet objet. Vous pouvez également utiliser l’mot clé static_cast pour convertir une valeur lvalue en référence rvalue, comme illustré dans l’exemple suivant :

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

Cet exemple produit la sortie suivante :

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Les modèles de fonction déduisent leurs types d’arguments de modèle, puis utilisent des règles de réduction de référence.

Un modèle de fonction qui transmet (ou transfère) ses paramètres à une autre fonction est un modèle courant. Il est important de comprendre comment fonctionne la déduction de type de modèle pour les modèles de fonction qui acceptent des références rvalue.

Si l'argument de fonction est une rvalue, le compilateur déduit l'argument comme étant une référence rvalue. Par exemple, supposons que vous transmettez une référence rvalue à un objet de type X à un modèle de fonction qui prend le type T&& comme paramètre. La déduction de l’argument de modèle est déduite T d’être X, de sorte que le paramètre a le type X&&. Si l’argument de fonction est une valeur lvalue ou const lvalue, le compilateur déduit son type comme référence lvalue ou const référence lvalue de ce type.

L'exemple suivant déclare un modèle de structure, puis le spécialise pour différents types de références. La fonction print_type_and_value accepte une référence rvalue comme paramètre et la transfère à la version spécialisée appropriée de la méthode S::print. La fonction main illustre les différentes façons d'appeler la méthode S::print.

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S<T&> {
   static void print(T& t)
   {
      cout << "print<T&>: " << t << endl;
   }
};

template<typename T> struct S<const T&> {
   static void print(const T& t)
   {
      cout << "print<const T&>: " << t << endl;
   }
};

template<typename T> struct S<T&&> {
   static void print(T&& t)
   {
      cout << "print<T&&>: " << t << endl;
   }
};

template<typename T> struct S<const T&&> {
   static void print(const T&& t)
   {
      cout << "print<const T&&>: " << t << endl;
   }
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
   S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
   // The following call resolves to:
   // print_type_and_value<string&>(string& && t)
   // Which collapses to:
   // print_type_and_value<string&>(string& t)
   string s1("first");
   print_type_and_value(s1);

   // The following call resolves to:
   // print_type_and_value<const string&>(const string& && t)
   // Which collapses to:
   // print_type_and_value<const string&>(const string& t)
   const string s2("second");
   print_type_and_value(s2);

   // The following call resolves to:
   // print_type_and_value<string&&>(string&& t)
   print_type_and_value(string("third"));

   // The following call resolves to:
   // print_type_and_value<const string&&>(const string&& t)
   print_type_and_value(fourth());
}

Cet exemple produit la sortie suivante :

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

Pour résoudre chaque appel à la print_type_and_value fonction, le compilateur effectue d’abord une déduction d’argument de modèle. Le compilateur applique ensuite des règles de réduction de référence lorsqu’il remplace les types de paramètres par les arguments de modèle déduits. Par exemple, le passage de la variable locale s1 à la fonction print_type_and_value provoque la production par le compilateur de la signature de fonction suivante :

print_type_and_value<string&>(string& && t)

Le compilateur utilise des règles de réduction de référence pour réduire la signature :

print_type_and_value<string&>(string& t)

Cette version de la fonction print_type_and_value transfère ensuite son paramètre à la version spécialisée appropriée de la méthode S::print.

Le tableau suivant résume les règles de réduction de référence pour la déduction du type d'argument template :

Type développé Type réduit
T& & T&
T& && T&
T&& & T&
T&& && T&&

La déduction d’argument template est un élément important de l’implémentation du transfert parfait. La section Transfert parfait décrit plus en détail le transfert parfait.

Résumé

Les références rvalue différencient les lvalues des rvalues. Pour améliorer les performances de vos applications, elles peuvent éliminer le besoin d’allocations de mémoire inutiles et d’opérations de copie. Ils vous permettent également d’écrire une fonction qui accepte des arguments arbitraires. Cette fonction peut les transférer vers une autre fonction comme si l’autre fonction avait été appelée directement.

Voir aussi

Expressions avec des opérateurs unaires
Déclarateur de référence Lvalue : &
Lvalues et rvalues
Déplacer des constructeurs et des opérateurs d’affectation de déplacement (C++)
Bibliothèque C++ standard