Références rvalue (C++11)

De RAD Studio
Aller à : navigation, rechercher

Remonter à Fonctionnalités C++11 - Index


BCC32 inclut l'utilisation des références rvalue qui permettent la création d'une référence aux temporaires. De plus, les références rvalue évitent la copie inutile et rendent possible les fonctions forward parfaites. Cette fonctionnalité fait partie du standard C++11.

Description

Les références rvalue représentent un type composé comme les références standard C++, qui sont désignées comme les références lvalue. Une référence lvalue est formée en ajoutant le caractère esperluette (&) à un type :

SomeClass l;
SomeClass& lReference = l;    //lvalue reference

La syntaxe d'une référence rvalue consiste à ajouter && après un type :

SomeClass r;
SomeClass&& rReference = r;    //rvalue reference

Une référence rvalue se comporte comme une référence lvalue, sauf que vous pouvez lier une référence rvalue à un temporaire -- un rvalue.

SomeClass a;
a = SomeClass();
SomeClass& lReference = a;         //OK-lvalue reference can bind to an lvalue such as "a"
SomeClass& lReference2 = SomeClass();  //error-lvalue reference can't bind to an rvalue
SomeClass&& rReference = SomeClass();  //OK for rvalue reference to bind to rvalue

// Both references can be used the same way
SomeOtherClass value1 = SomeFunc(lReference);
SomeOtherClass value2 = SomeFunc(rReference);

Dans l'exemple ci-dessus, SomeClass() n'est pas lié à un identificateur. C'est donc un rvalue et il peut être lié à une référence rvalue -- mais pas à une référence lvalue.

Une référence rvalue ne peut pas être liée à un lvalue nommé

Les nouvelles règles des références rvalue ont été définies par la spécification C++. Les règles de liaison pour les références rvalue fonctionnent maintenant différemment sur un point. En bref, une référence rvalue ne plus être liée à un lvalue nommé (excepté dans la résolution de surcharges, ce qui n'a pas changé).

Les en-têtes Dinkumware suivent toujours les anciennes règles, vous devez ainsi compiler avec la nouvelle option -Vbv si vous projetez d'utiliser les fonctionnalités de référence rvalue des en-têtes Dinkumware.

Elimination de la copie inutile

Dans la plupart des cas, des données qui doivent seulement être déplacées sont copiées, c'est-à-dire que le conteneur des données d'origine ne doit pas conserver les données. Un exemple est la permutation des données dans deux structures, ainsi aucune des structures ne contient les données précédentes. Il serait logiquement suffisant de permuter simplement les références aux données.

Les références rvalue peuvent être utilisées pour distinguer les cas qui nécessitent une copie de ceux qui nécessitent simplement le déplacement des données. Etant donné que la copie est une opération longue, vous souhaiterez l'éviter autant que possible.

Si une fonction souhaite copier quelque chose qui lui a été passé en tant que rvalue, elle peut alors faire un déplacement au lieu d'une copie, car elle sait que la valeur est temporaire. Si un lvalue a été transmis à la fonction, il peut s'avérer nécessaire de faire une copie complète car le remplacement de la copie ne s'applique pas. Vous pouvez distinguer ces cas avec la signature de fonction.

Considérons une classe ExampleClass ayant une fonction clone qui effectue une copie complète des instances de classe. Vous pouvez définir une fonction move qui déplace une valeur d'objet. Cette fonction peut être surchargée comme suit :

// Parameter is lvalue
ExampleClass move(ExampleClass& l)
{
    return l.clone();  //returns a copy since we can't touch the original value

// Parameter is rvalue
ExampleClass move(ExampleClass&& r)
{
    return r;      //returns a reference since we don't care about the temporary's value
}
template class MyClass<int>;
...
extern template class MyClass<int>;    // not allowed
extern template class MyClass<float>;  // OK

Nous pouvons ensuite utiliser la fonction move pour les rvalues et lvalues :

ExampleClass a, b, c;
a = ExampleClass();
b = b.move(a);         //parameter is lvalue
c = c.move(ExampleClass());    //parameter is rvalue

Notez que la fonction move pour le paramètre rvalue est très petite afin qu'elle s'exécute beaucoup plus rapidement que la fonction move pour un paramètre lvalue.

Vous pouvez utiliser une technique similaire pour les fonctions qui nécessitent des copies, comme les constructeurs de copie et les opérateurs d'affectation. Supposons que nous ayons une classe template avec un pointeur sur une autre classe, où clone est de nouveau une fonction de copie complète :

template <class T>
class PointerClass
{
private:
    T* pointer;
public:
    // Regular constructor
    PointerClass(void);

    // Copy constructor for lvalues
    PointerClass(PointerClass& pcl) : pointer(pcl.pointer ? pcl.pointer.clone() : 0) {}    //make full copy
    // Copy constructor for rvalues
    PointerClass(PointerClass&& pcr) : pointer(pcr.pointer) {pcr.pointer = 0;}

Le constructeur de copie qui prend un rvalue :

  • Effectue un déplacement, pas une copie. Il renvoie simplement une référence aux données.
  • Traite l'argument rvalue pcr comme un lvalue dans son code.
  • Laisse l'objet rvalue dans un état défini afin qu'il puisse être supprimé en toute sécurité.

Types non copiables mais déplaçables

Les types qui ne sont pas copiables, comme ceux utilisant unique_ptr, peuvent être rendus déplaçables. Bien qu'ils ne puissent pas définir des opérateurs d'assignation, ils peuvent implémenter des fonctions de déplacement et des fonctions de permutation, puisque celles-ci ne nécessitent pas la copie quand des références rvalue sont utilisées. Une fonction de tri pourrait être développée, puisqu'elle requiert seulement la permutation, et pas la copie.

Par exemple, considérons la fonction factory qui prend un argument :

template <class T, class U>
factory(const U& u)
{
    return new T(u);
}

La définition ci-dessus de factory fonctionne dans ce cas :

T* p = factory<T>(7);

Toutefois, une erreur de compilation survient quand un T, dont le paramètre du constructeur est une référence non-const, est utilisé. Vous pouvez corriger ce cas en retirant const de la définition :

template <class T, class U>
factory(U& u)
{
    return new T(u);
}

Toutefois, l'exemple précédent échoue maintenant :

T* p = factory<T>(7); // compiler error
T* u = new T(7);   //OK

Cela génère une erreur puisque la valeur 7 provoque la correspondance de l'argument template sur int &, mais cela n'effectue pas la liaison au rvalue 7.

Ceci peut être résolu en définissant une fonction factory pour chaque cas de const et non-const. C'est toutefois problématique, car le nombre de fonctions nécessaires augmente de manière exponentielle avec le nombre d'arguments.

Si l'argument devient une référence rvalue, vous pouvez simplifier la situation :

template <class T, class U>
factory(u&& u)
{
   return new T(forward<U>(u));
}

L'argument u est maintenant lié aux rvalues et lvalues. La fonction forward renvoie un rvalue ou un lvalue, exactement comme s'il avait été transmis. Elle peut être définie de cette façon :

template <class U>
struct identity
{
    typedef U type;
};

template <class U>
U&& forward(typename identity<U>::type&& u)
{
    return u;
}

Voir aussi