Que signifie T&& (double ampersand) en C++11?

j'ai examiné quelques-unes des nouvelles fonctionnalités de C++11 et j'ai remarqué le double ampersand dans les variables déclarantes, comme T&& var .

pour commencer, comment s'appelle cette bête? J'aimerais que Google nous permette de rechercher une ponctuation comme celle-ci.

que signifie exactement ?

à première vue, il semble être une double référence (comme le C-style Double pointeurs T** var ), mais j'ai du mal à penser à un cas d'utilisation pour cela.

630
demandé sur paxdiablo 2011-03-30 07:29:58

4 réponses

Il déclare référence rvalue (proposition de normes doc).

Voici une introduction à la rvalue références .

voici un regard fantastique en profondeur sur les références rvalue par L'une des bibliothèques standard de Microsoft développeurs . (Mais voir la mise en garde dans les commentaires qui suivent cette réponse avant de lire cet article.)

le plus grand la différence entre une référence c++03 (maintenant appelée référence lvalue en C++11) est qu'elle peut se lier à une valeur R comme une référence temporaire sans avoir à être const. Ainsi, cette syntaxe est maintenant légale:

T&& r = T();

références rvalue principalement les suivantes:

sémantique de Déplacement . Un constructeur de mouvements et un opérateur d'affectation de mouvements peuvent maintenant être définis qui prend une référence rvalue au lieu de la valeur constante habituelle référence. Un déplacement des fonctions comme une copie, sauf qu'il n'est pas obligé de garder la source inchangée; en fait, il modifie la source de sorte qu'il ne possède plus le déplacé ressources. Ceci est idéal pour éliminer les copies externes, en particulier dans les implémentations de bibliothèques standard.

Par exemple, un constructeur de copie pourrait ressembler à ceci:

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

si ce constructeur était passé un temporaire, la copie serait inutile parce que nous savoir temporaire sera détruit; pourquoi ne pas utiliser les ressources temporaires déjà alloué? Dans C++03, il n'y a aucun moyen d'empêcher la copie car nous ne pouvons pas déterminer que nous avons passé une passagère. En C++11, on peut surcharger un constructeur de mouvements:

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

remarquez la grande différence ici: le constructeur move modifie en fait son argument. Cela "déplacerait" effectivement le temporaire dans l'objet en construction, éliminant ainsi l'inutile copie.

le constructeur move serait utilisé pour les références temporelles et non-converties en références de valeur qui sont explicitement converties en références de valeur en utilisant la fonction std::move (il ne fait que la conversion). Le code suivant invoque à la fois le constructeur move pour f1 et f2 :

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

Perfect forwarding . les références rvalue nous permettent de transmettre correctement les arguments pour templated fonction. Prenez par exemple cette fonction d'usine:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

si nous avons appelé factory<foo>(5) , l'argument sera déduit d'être int& , qui ne se liera pas à un 5 littéral, même si foo 's constructeur prend un int . Nous pourrions utiliser A1 const& , mais que faire si foo prend l'argument du constructeur par référence non-const? Pour faire une fonction d'usine vraiment générique, nous devrions surcharger l'usine sur A1& et sur A1 const& . Cela pourrait être bien si l'usine prend 1 Type de paramètre, mais chaque type de paramètre supplémentaire multiplierait la surcharge nécessaire réglée par 2. C'est très rapidement désuète.

rvalue references corriger ce problème en permettant à la bibliothèque standard de définir une fonction std::forward qui peut transmettre correctement les références lvalue/rvalue. Pour plus d'informations sur le fonctionnement de std::forward , voir cette excellente réponse .

cela nous permet de définir la fonction d'usine comme ceci:

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

maintenant la valeur de l'argument rvalue / lvalue-ness est conservée lorsqu'elle est passée au constructeur de T . Cela signifie que si factory est appelé avec une valeur R, le constructeur de T est appelé avec une valeur R. Si factory est appelé avec une valeur l, le constructeur de T est appelé avec une valeur l. La fonction d'usine améliorée fonctionne à cause d'une règle spéciale:

lorsque le type de paramètre de fonction est de le formulaire T&&T est un modèle paramètre, et l'argument de fonction est une valeur l de type A , le type A& est utilisé pour la déduction d'argument de modèle.

Ainsi, nous pouvons utiliser l'usine comme suit:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

Important référence rvalue propriétés :

  • Pour la résolution de surcharge, lvalues préfèrent la liaison lvalue des références et des rvalues préfèrent la liaison références rvalue . D'où la préférence des temporairesà invoquer un opérateur d'affectation move constructor / move assignment plutôt qu'un opérateur de copie constructor / assignment.
  • références rvalue sera implicitement se lier à des rvalues et temporaires qui sont le résultat d'une conversion implicite . c'est à dire float f = 0f; int&& i = f; est bien formé parce que float est implicitement convertible en int; la référence serait à un temporaire qui est le résultat de la conversion.
  • Nommé références rvalue sont lvalues. Les références sans nom rvalue sont rvalues. il est important de comprendre pourquoi l'appel std::move est nécessaire dans: foo&& r = foo(); foo f = std::move(r);
558
répondu Peter Huene 2017-07-31 12:56:38

indique une valeur de référence. Les références de valeur ne se lieront qu'aux objets temporaires, sauf si elles sont explicitement générées autrement. Ils sont utilisés pour rendre les objets beaucoup plus efficaces dans certaines circonstances, et pour fournir une facilité connue sous le nom de Perfect forwarding, qui simplifie grandement le code de modèle.

En C++03, vous ne pouvez pas distinguer entre, d'une copie d'un non-mutable lvalue et une rvalue.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

en C++0x, ce n'est pas cas.

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

envisager la mise en œuvre derrière ces constructeurs. Dans le premier cas, la chaîne doit effectuer une copie pour conserver la sémantique des valeurs, ce qui implique une nouvelle allocation de tas. Cependant, dans le second cas, nous savons à l'avance que l'objet qui a été transmis à notre constructeur doit être immédiatement détruit, et il ne doit pas rester intact. Nous pouvons simplement échanger les pointeurs internes et ne pas effectuer de copie du tout dans ce le scénario, ce qui est nettement plus efficace. La sémantique de déplacement profite à toute classe qui a la copie coûteuse ou interdite de ressources référencées à l'interne. Prenons le cas de std::unique_ptr - maintenant que notre classe peut faire la distinction entre les temporaireset les non-temporels, nous pouvons faire fonctionner correctement la sémantique du mouvement de sorte que le unique_ptr ne peut pas être copié mais peut être déplacé, ce qui signifie que std::unique_ptr peut être stocké légalement dans des conteneurs Standard, triés, etc, tandis que C++03 std::auto_ptr " ne peut pas.

maintenant, nous considérons l'autre utilisation des références de valeur-perfect forwarding. Examinez la question de lier une référence à une référence.

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

ne peut pas se rappeler ce que dit C++03 à ce sujet, mais dans C++0x, le type résultant lors de la gestion des références rvalue est critique. Une référence de valeur à un type T, Où T est un type de référence, devient une référence de type T.

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

considèrent le plus simple fonction modèle-min et max. En C++03, vous devez surcharger manuellement les quatre combinaisons de const et non-const. En C++0x, c'est juste une surcharge. Combiné avec des gabarits variadiques, cela permet une transmission parfaite.

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

j'ai abandonné la déduction de type déclaration, parce que je ne peux pas me rappeler comment c'est fait à la main, mais que min peut accepter n'importe quelle combinaison de lvalues, rvalues, const lvalues.

76
répondu Puppy 2014-10-06 17:35:51

le terme pour T&& lorsqu'il est utilisé avec la déduction de type (comme pour Perfect forwarding) est connu familièrement comme " forwarding reference . Le terme "référence universelle" a été inventé par Scott Meyers dans cet article , mais a été plus tard changé.

, c'est-à-dire qu'il peut s'agir d'une valeur r ou d'une valeur L.

exemples:

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

plus la discussion peut être trouvée dans la réponse pour: syntaxe pour les références universelles

18
répondu mmocny 2018-09-10 15:04:23

une référence rvalue est un type qui se comporte un peu comme la référence ordinaire X&, avec plusieurs exceptions. Le plus important est qu'en ce qui concerne la résolution de la surcharge de fonction, lvalues préfère les références lvalue à l'ancienne, tandis que rvalues préfère les nouvelles références rvalue:

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

Qu'est-ce qu'une valeur R? Tout ce qui n'est pas une lvalue. Une lvalue être une expression qui fait référence à un emplacement de mémoire et nous permet de prendre l'adresse de ce l'emplacement de la mémoire via l'opérateur&.

il est presque plus facile de comprendre d'abord ce que rvalues accomplir avec un exemple:

 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {}
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      ptr = new int[s.size]; 
      size = s.size; 
    }
    cout << "Copy Assignment called on lvalue." << std::endl;
    return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};     

le constructeur et les opérateurs d'affectation ont été surchargés avec des versions qui prennent des références de valeur. Les références Rvalue permettent à une fonction de se brancher au moment de la compilation (via une résolution de surcharge) à la condition "suis-je appelé sur une lvalue ou une rvalue?". cela nous a permis de créer plus le constructeur efficace et les opérateurs d'affectation ci-dessus qui déplacent les ressources plutôt les copier.

le compilateur se ramifie automatiquement au moment de la compilation (selon qu'il est invoqué pour une valeur L ou une valeur R) En choisissant si le constructeur de mouvements ou l'opérateur d'affectation de mouvements doit être appelé.

en résumé: les références rvalue autoriser la sémantique de déplacement (et de transfert parfait, discuté dans l'article en lien ci-dessous).

un exemple pratique facile à comprendre est le modèle de classe std::unique_ptr . Puisqu'un unique_ptr conserve la propriété exclusive de son pointeur brut sous-jacent, unique_ptr ne peut pas être copié. Cela violerait leur invariant de propriété exclusive. Ils n'ont pas de copie des constructeurs. Mais ils ont des constructeurs move:

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 

static_cast<unique_ptr<int[]>&&>(ptr) est habituellement fait en utilisant std:: move

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

un excellent article expliquant tout cela et plus encore (comme la façon dont les valeurs de R permettent l'expédition parfaite et ce que cela signifie) avec beaucoup de bons exemples est de Thomas Becker C++ Rvalue les références expliquées . Ce post est fortement appuyée sur son article.

une brève introduction est une brève Introduction aux références de valeur par Stroutrup, et. al

8
répondu kurt krueckeberg 2016-11-12 17:02:08