1
votes

Comprendre l'expression lvalue / rvalue par rapport au type d'objet

J'ai lu quelques-unes des principales réponses précédentes ainsi que "The C ++ Programming Language" et "Effective C ++ moderne" de Stroustrup, mais j'ai du mal à vraiment comprendre la distinction entre l'aspect lvalue / rvalue d'une expression et son taper. Dans l'introduction à «Effective Modern C ++», il est dit:

Une heuristique utile pour déterminer si une expression est une lvalue est de demander si vous pouvez prendre son adresse. Si vous le pouvez, c'est généralement le cas. Si vous ne pouvez pas, c'est généralement une rvalue. Une fonctionnalité intéressante de cette heuristique est qu'elle vous aide à vous rappeler que le type d'une expression est indépendant du fait que l'expression soit une lvalue ou une rvalue ... Il est particulièrement important de s'en souvenir lorsque vous traitez avec un paramètre de type de référence rvalue, car le le paramètre lui-même est une valeur l.

Je ne comprends pas quelque chose car je ne comprends pas pourquoi si vous avez un paramètre de type de référence rvalue, vous devez réellement le convertir en rvalue via std :: move () code > pour le rendre éligible au déménagement. Même si le paramètre (tous les paramètres) est une lvalue, le compilateur sait que son type est une référence rvalue, alors pourquoi le besoin de dire au compilateur qu'il peut être déplacé? Cela semble redondant mais je suppose que je ne comprends pas la distinction entre le type d'une expression et sa nature lvalue / rvalue (pas sûr de la bonne terminologie).

Edit:

Pour faire suite à certaines des réponses / commentaires ci-dessous, ce qui n'est toujours pas clair, c'est pourquoi dans doSomething () ci-dessous, j'aurais besoin d'envelopper le paramètre dans std :: move () pour qu'il se lie à une référence rvalue et se résolve à la 2ème version de doSomethingElse () . Je comprends que si cela se produisait implicitement, ce serait mauvais parce que le paramètre aurait été déplacé et que l'on pourrait l'utiliser par inadvertance après cela. Il semble que la nature du type de référence rvalue du paramètre n'a pas de sens dans la fonction car son seul but était de se lier pour résoudre la bonne version de la fonction étant donné qu'une rvalue a été passée en argument.

Widget getWidget();
void doSomethingElse(Widget& rhs);  // #1
void doSomethingElse(Widget&& rhs); // #2

void doSomething(Widget&& rhs) {
  // will call #1
  doSomethingElse(rhs);
  // will call #2
  doSomethingElse(std::move(rhs));      
}

int main() {
  doSomething(getWidget());
}


3 commentaires

Il semble y avoir une certaine confusion fondamentale ici; "références" appartiennent aux types , et lvalue / rvalue appartient aux expressions . Les expressions ne sont jamais des références et les références ne sont jamais des expressions. Les références Lvalue lient les lvalues ​​et les réfrences rvalue lient les rvalues, c'est ce que leurs noms signifient.


@KerrekSB Ce n'est pas vraiment aussi simple; prenez un const T & par exemple.


@LightnessRacesinOrbit: ... soupir, oui, bien sûr, mais en première approximation. Je voulais souligner l'erreur conceptuelle de haut niveau sans brouiller l'image avec trop de détails.


3 Réponses :


0
votes

Des références RValue existent pour résoudre le problème de transfert. Les règles de déduction de type existantes en C ++ ont rendu impossible une sémantique de déplacement cohérente et sensée. Ainsi, le système de types a été étendu et de nouvelles règles ont été introduites pour le rendre plus compliqué mais cohérent.

Cela n'a de sens que si vous l'examinez à travers la perspective du problème qui a été résolu. Voici un bon lien dédié uniquement à l'explication des références RValue.


0 commentaires

2
votes

Je ne comprends pas pourquoi si vous avez un paramètre de type de référence rvalue, vous devez le convertir en rvalue via std :: move () pour le rendre éligible au déplacement. p>

Comme le disent les citations, types et les catégories de valeurs sont des choses différentes. Un paramètre est toujours une lvalue, même son type est une rvalue-reference; nous devons utiliser std :: move pour le lier à une rvalue-reference. Supposons que nous permettions au compilateur de le faire implicitement, comme l'extrait de code suivant,

void bar(std::string&& s) {

    foo(std::move(s));  

    // we know that s might have been moved
}

Nous devons donc utiliser std :: move explicitement, pour dites au compilateur que nous savons ce que nous essayons de faire.

void foo(std::string&& s);
void bar(std::string&& s) {

    foo(s);  

    // continue to use s...
    // oops, s might have been moved

    foo(std::string{}); // this is fine;
                        // the temporary will be destroyed after the full expression and won't be used later

}

0 commentaires

0
votes

Je pense que vous avez réellement compris la distinction entre type et catégorie de valeur , je vais donc me concentrer sur deux revendications / requêtes spécifiques:

Vous devez le convertir en rvalue via std :: move () pour le rendre éligible pour être déplacé

En quelque sorte, mais pas vraiment. Le fait de forcer l'expression qui nomme ou fait référence à votre objet dans une rvalue nous permet de déclencher, lors de la résolution de surcharge, la surcharge de fonction qui prend un Type && . C'est une convention que nous faisons cela lorsque nous voulons transférer la propriété, mais ce n'est pas tout à fait la même chose que de le rendre «éligible au déménagement» parce qu'un déménagement n'est peut-être pas ce que vous finissez par faire. C'est une sorte de pinaillage dans un sens, même si je pense qu'il est important de comprendre. Parce que:

Même si le paramètre (tous les paramètres) est une lvalue, le compilateur sait que son type est une référence rvalue, alors pourquoi est-il nécessaire de dire au compilateur qu'il peut être déplacé?

À moins que vous n'écriviez std :: move (theThing) , ou que l'objet soit temporaire (déjà une rvalue), alors ce n'est pas une rvalue, et donc il ne peut pas se lier à une référence rvalue. C'est ainsi que tout est conçu et défini. C'est délibérément fait de cette façon qu'une expression lvalue, une expression qui nomme une chose, une chose sur laquelle vous n'avez pas écrit std :: move () autour, ne sera pas liée à une rvalue référence . Et donc soit votre programme ne compilera pas, soit, si disponible, la résolution de surcharge choisira à la place une version de la fonction qui prend peut-être un const Type & - et nous savons qu'il ne peut y avoir aucun transfert de propriété impliqué avec cela .

tl; dr: le compilateur ne sait pas que son type est une référence rvalue, car il n'en est pas une. Tout comme vous ne pouvez pas faire int & ref = 42 , vous ne pouvez pas faire int x = 42; int && ref = x; . Sinon, il essaierait de déplacer tout ! Le but est de faire en sorte que certains types de références ne fonctionnent qu'avec certains types d'expressions, afin que nous puissions l'utiliser pour déclencher des appels à des fonctions de copie / déplacement de manière appropriée avec un minimum de machines sur le site d'appel.


0 commentaires