229 votes

Qu'est-ce que "rvalue reference for *this" ?

Je suis tombé sur une proposition appelée "rvalue reference for *this" dans le document clang's Page d'état C++11 .

J'ai lu pas mal de choses sur les références rvalue et je les ai comprises, mais je ne pense pas être au courant de ceci. Je n'ai pas non plus trouvé beaucoup de ressources sur le web utilisant ces termes.

Il y a un lien vers le document de proposition sur la page : N2439 (Extension de la sémantique du déplacement à *this), mais je ne trouve pas non plus beaucoup d'exemples à partir de là.

De quoi s'agit-il ?

284voto

Xeo Points 69818

Tout d'abord, l'expression "qualificatifs de référence pour *ceci" n'est qu'une "déclaration de marketing". Le type de *this ne change jamais, voir le bas de ce post. C'est beaucoup plus facile à comprendre avec cette formulation.

Ensuite, le code suivant choisit la fonction à appeler en se basant sur l'attribut ref-qualifier du "paramètre objet implicite" de la fonction † :

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

Sortie :

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

Tout cela est fait pour vous permettre de profiter du fait que l'objet sur lequel la fonction est appelée est une rvalue (temporaire sans nom, par exemple). Prenons le code suivant comme autre exemple :

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

C'est peut-être un peu artificiel, mais vous devriez comprendre l'idée.

Notez que vous pouvez combiner les cv-qualificateurs ( const y volatile ) y ref-qualificateurs ( & y && ).


Note : Beaucoup de citations standard et d'explication de résolution de surcharge après ici !

† Pour comprendre comment cela fonctionne, et pourquoi la réponse de @Nicol Bolas est au moins partiellement fausse, nous devons creuser un peu dans la norme C++ (la partie expliquant pourquoi la réponse de @Nicol est fausse se trouve en bas, si cela ne vous intéresse que cela).

La fonction qui va être appelée est déterminée par un processus appelé résolution des surcharges . Ce processus est assez compliqué, nous n'aborderons donc que la partie qui est importante pour nous.

Tout d'abord, il est important de voir comment fonctionne la résolution des surcharges pour les fonctions membres :

§13.3.1 [over.match.funcs]

p2 L'ensemble des fonctions candidates peut contenir à la fois des fonctions membres et non membres à résoudre par rapport à la même liste d'arguments. Ainsi, les listes d'arguments et de paramètres sont comparables au sein de cet ensemble hétérogène, une fonction membre est considérée comme ayant un paramètre supplémentaire, appelé paramètre implicite de l'objet, qui représente l'objet pour lequel la fonction membre a été appelée. . [...]

p3 De même, lorsque cela est approprié, le contexte peut construire une liste d'arguments qui contient un argument implicite de l'objet pour désigner l'objet à traiter.

Pourquoi avons-nous besoin de comparer les fonctions des membres et des non-membres ? La surcharge d'opérateurs, voilà pourquoi. Considérez ceci :

struct foo{
  foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

Vous voudriez certainement que le suivant appelle la fonction libre, n'est-ce pas ?

char const* s = "free foo!\n";
foo f;
f << s;

C'est pourquoi les fonctions membres et non membres sont incluses dans ce qu'on appelle l'ensemble des surcharges. Pour rendre la résolution moins compliquée, la partie en gras de la citation standard existe. De plus, c'est la partie importante pour nous (même clause) :

p4 Pour les fonctions membres non statiques, le type du paramètre implicite de l'objet est

  • "Référence de la valeur l à cv X "pour les fonctions déclarées sans un ref-qualifier ou avec le & ref-qualifier

  • "rvalue" référence à cv X "pour les fonctions déclarées avec la balise && ref-qualifier

X est la classe dont la fonction est membre et cv est la qualification cv de la déclaration de la fonction membre. [...]

p5 Pendant la résolution de la surcharge [...] [l]e paramètre objet implicite [...] conserve son identité puisque les conversions sur l'argument correspondant doivent obéir à ces règles supplémentaires :

  • aucun objet temporaire ne peut être introduit pour contenir l'argument du paramètre implicite de l'objet ; et

  • aucune conversion définie par l'utilisateur ne peut être appliquée pour obtenir une correspondance de type avec celui-ci

[...]

(La dernière partie signifie simplement que vous ne pouvez pas tromper la résolution de surcharge basée sur des conversions implicites de l'objet sur lequel une fonction membre (ou un opérateur) est appelée).

Prenons le premier exemple en haut de cet article. Après la transformation susmentionnée, le jeu de surcharges ressemble à quelque chose comme ceci :

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

Ensuite, la liste des arguments, contenant un argument implicite de l'objet est comparée à la liste de paramètres de chaque fonction contenue dans l'ensemble de surcharge. Dans notre cas, la liste des arguments ne contiendra que l'argument objet. Voyons à quoi cela ressemble :

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
       // kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
       // taken out of overload-set

Si, après avoir testé toutes les surcharges de l'ensemble, il n'en reste qu'une, la résolution de la surcharge a réussi et la fonction liée à cette surcharge transformée est appelée. Il en va de même pour le deuxième appel à 'f' :

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
            // taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
            // kept in overload-set

Notez cependant que, si nous n'avions pas fourni de ref-qualifier (et en tant que tel n'a pas surchargé la fonction), que f1 serait correspond à une rvalue (toujours §13.3.1 ):

p5 [...] Pour les fonctions membres non statiques déclarées sans un ref-qualifier une règle supplémentaire s'applique :

  • même si le paramètre implicite de l'objet n'est pas const -qualifié, une rvalue peut être liée au paramètre tant que, à tous autres égards, l'argument peut être converti au type du paramètre objet implicite.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

Maintenant, voyons pourquoi la réponse de @Nicol est au moins partiellement fausse. Il dit :

Notez que cette déclaration change le type de *this .

C'est faux, *this es toujours une lvalue :

§5.3.1 [expr.unary.op] p1

L'unaire * L'opérateur effectue indirection l'expression à laquelle il est appliqué doit être un pointeur vers un type d'objet ou un pointeur vers un type de fonction. et le résultat est une lvalue se référant à l'objet ou à la fonction vers laquelle l'expression pointe.

§9.3.2 [class.this] p1

Dans le corps d'une fonction membre non statique (9.3), le mot-clé this est une expression prvalue dont la valeur est l'adresse de l'objet pour lequel la fonction est appelée. Le type de this dans une fonction membre d'une classe X es X* . [...]

77voto

JohannesD Points 4935

Il existe un cas d'utilisation supplémentaire pour la forme lvalue ref-qualifier. C++98 possède un langage qui permet aux non const les fonctions membres à appeler pour les instances de la classe qui sont des rvalues. Cela conduit à toutes sortes de bizarreries qui vont à l'encontre du concept même de valeur et s'écartent du fonctionnement des types intégrés :

struct S {
  S& operator ++(); 
  S* operator &(); 
};
S() = S();      // rvalue as a left-hand-side of assignment!
S& foo = ++S(); // oops, dangling reference
&S();           // taking address of rvalue...

Les ref-qualificateurs Lvalue résolvent ces problèmes :

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};

Maintenant, les opérateurs fonctionnent comme ceux des types intégrés, en acceptant seulement les valeurs l.

28voto

Nicol Bolas Points 133791

Disons que vous avez deux fonctions sur une classe, toutes deux avec le même nom et la même signature. Mais l'une d'entre elles est déclarée const :

void SomeFunc() const;
void SomeFunc();

Si une instance de classe n'est pas const la résolution des surcharges sélectionnera de préférence la version non-const. Si l'instance est const l'utilisateur ne peut appeler que le const version. Et le this Le pointeur est un const de sorte que l'instance ne peut pas être modifiée.

Ce que fait "référence à la valeur r pour ceci" est de vous permettre d'ajouter une autre alternative :

void RValueFunc() &&;

Cela vous permet d'avoir une fonction qui peut uniquement sera appelé si l'utilisateur l'appelle par une valeur r appropriée. Donc si c'est dans le type Object :

Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.

De cette façon, vous pouvez spécialiser le comportement en fonction de l'accès à l'objet via une valeur r ou non.

Notez que vous n'êtes pas autorisé à surcharger entre les versions de référence à valeur r et les versions sans référence. C'est-à-dire que si vous avez un nom de fonction membre, toutes ses versions utilisent soit les qualificatifs l/r-value sur this ou aucun d'entre eux ne le fait. Vous ne pouvez pas faire ça :

void SomeFunc();
void SomeFunc() &&;

Vous devez le faire :

void SomeFunc() &;
void SomeFunc() &&;

Notez que cette déclaration change le type de *this . Cela signifie que le && versions de tous les membres d'accès en tant que références de valeur r. Il devient donc possible de se déplacer facilement à l'intérieur de l'objet. L'exemple donné dans la première version de la proposition est le suivant (note : ce qui suit peut ne pas être correct avec la version finale de C++11 ; c'est directement issu de la proposition initiale "r-value from this") :

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X