En C++ 03, une expression est soit une valeur rvalue , soit une lvalue .
En C++ 11, une expression peut être un:
Deux catégories sont devenues cinq catégories.
Je suppose que ce document pourrait constituer une introduction pas si courte: n3055
Tout le massacre a commencé avec la sémantique du mouvement. Une fois que nous avons des expressions qui peuvent être déplacées et non copiées, il est soudainement facile de saisir les règles qui exigent une distinction entre les expressions pouvant être déplacées et dans quelle direction.
D'après ce que je suppose d'après le brouillon, la distinction entre les valeurs de r/l reste la même, mais uniquement lorsque les choses bougent.
Sont-ils nécessaires? Probablement pas si nous souhaitons renoncer aux nouvelles fonctionnalités. Mais pour permettre une meilleure optimisation, nous devrions probablement les adopter.
Citations n3055 :
E
est une expression de type pointeur, alors *E
est une expression lvalue faisant référence à l'objet ou à la fonction sur laquelle E
pointe. Autre exemple, l'appel d'une fonction dont le type de retour est une référence à lvalue est une lvalue.] Le document en question est une excellente référence pour cette question, car il indique les modifications exactes apportées à la norme suite à l’introduction de la nouvelle nomenclature.
Quelles sont ces nouvelles catégories d'expressions?
Le FCD (n3092) a une excellente description:
- Une lvalue (ainsi appelée historiquement, car des valeurs pourraient apparaître à gauche d'une expression d'affectation) désigne une fonction ou un objet. [Exemple: Si E est une expression de type pointeur, alors * E est une expression lvalue faisant référence à l'objet ou à la fonction sur laquelle E pointe. Autre exemple, l'appel d'une fonction dont le type de retour est une référence à lvalue est une lvalue. —Fin exemple]
- Une valeur x (une valeur "expirante") fait également référence à un objet, généralement vers la fin de sa durée de vie (pour que ses ressources puissent être déplacées, par exemple). Une valeur x est le résultat de certains types d'expressions impliquant des références rvalue (8.3.2). [Exemple: le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une valeur x. —Fin exemple]
- Une glvalue (lvalue "généralisée") est une lvalue ou une xvalue.
- Une rvalue (ainsi appelée historiquement parce que des rvalues peuvent apparaître à droite des expressions d'affectation) est une xvalue, un objet temporaire (12.2) ou un sous-objet de celui-ci, ou une valeur qui n'est pas associée à un objet.
- Une valeur (valeur "pure") est une valeur qui n'est pas une valeur x. [Exemple: le résultat de l'appel d'une fonction dont le type de retour n'est pas une référence est une valeur. La valeur d'un littéral tel que 12, 7.3e5 ou true est également une valeur. —Fin exemple]
Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie: lvalue, xvalue ou prvalue. Cette propriété d'une expression s'appelle sa catégorie de valeur. [Remarque: la discussion de chaque opérateur intégré dans l'Article 5 indique la catégorie de la valeur qu'il génère et les catégories de valeur des opérandes qu'il attend. Par exemple, les opérateurs d'affectation intégrés s'attendent à ce que l'opérande gauche soit une valeur et que l'opérande droit soit une valeur et génère une valeur comme résultat. Les opérateurs définis par l'utilisateur sont des fonctions et les catégories de valeurs qu'ils attendent et qu'elles génèrent sont déterminées par leurs types de paramètre et de retour. —Fin note
Je vous suggère cependant de lire toute la section 3.10 Lvalues et rvalues .
Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?
Encore:
Les catégories rvalue et lvalue dans C++ 0x sont-elles les mêmes que dans C++ 03?
La sémantique des valeurs a particulièrement évolué avec l'introduction de la sémantique des déplacements.
Pourquoi ces nouvelles catégories sont-elles nécessaires?
Donc, cette construction/assignation de déménagement pourrait être définie et supportée.
Je commencerai par votre dernière question:
Pourquoi ces nouvelles catégories sont-elles nécessaires?
La norme C++ contient de nombreuses règles qui traitent de la catégorie de valeur d'une expression. Certaines règles font une distinction entre lvalue et rvalue. Par exemple, en matière de résolution de surcharge. D'autres règles distinguent glvalue et prvalue. Par exemple, vous pouvez avoir une valeur glvalue avec un type incomplet ou abstrait, mais aucune variable prvalue avec un type incomplet ou abstrait. Avant d’avoir cette terminologie, les règles qui devaient réellement distinguer glvalue/prvalue désignaient lvalue/rvalue et elles étaient soit involontairement fausses, soit comportaient de nombreuses explications et exceptions à la règle "... sauf si la valeur est due à un nom référence rvalue ... ". Donc, il semble être une bonne idée de simplement donner aux concepts de glvalues et de prvalues leur propre nom.
Quelles sont ces nouvelles catégories d'expressions? Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?
Nous avons toujours les termes lvalue et rvalue compatibles avec C++ 98. Nous venons de diviser les rvalues en deux sous-groupes, xvalues et prvalues, et nous appelons lvalues et xvalues en tant que glvalues. Les valeurs X constituent un nouveau type de catégorie de valeur pour les références rvalue non nommées. Chaque expression est l'une de ces trois valeurs: lvalue, xvalue, prvalue. Un diagramme de Venn ressemblerait à ceci:
______ ______
/ X \
/ / \ \
| l | x | pr |
\ \ / /
\______X______/
gl r
Exemples avec fonctions:
int prvalue();
int& lvalue();
int&& xvalue();
Mais n'oubliez pas que les références rvalue nommées sont des lvalues:
void foo(int&& t) {
// t is initialized with an rvalue expression
// but is actually an lvalue expression itself
}
Pourquoi ces nouvelles catégories sont-elles nécessaires? Est-ce que les dieux du WG21 essaient juste de nous confondre, simples mortels?
Je ne pense pas que les autres réponses (même si bon nombre d’entre elles) reflètent réellement la réponse à cette question particulière. Oui, ces catégories et telles existent pour permettre la sémantique de déplacement, mais la complexité existe pour une raison. C’est la seule règle inviolable de déplacer des éléments en C++ 11:
Tu ne bougeras que lorsqu'il est indiscutablement sûr de le faire.
C’est la raison pour laquelle ces catégories existent: pouvoir parler de valeurs où il est sans danger de s’en éloigner et de parler de valeurs où ce n’est pas.
Dans la version la plus ancienne des références de valeur r, le mouvement se faisait facilement. Trop facilement. Assez facilement, il y avait beaucoup de potentiel pour déplacer implicitement des choses lorsque l'utilisateur ne le voulait pas vraiment.
Voici les circonstances dans lesquelles il est sécuritaire de déplacer quelque chose:
Si tu fais ça:
SomeType &&Func() { ... }
SomeType &&val = Func();
SomeType otherVal{val};
Qu'est-ce que cela fait? Dans les anciennes versions de la spécification, avant que les 5 valeurs ne soient entrées, cela provoquerait un déplacement. Bien sûr que si. Vous avez passé une référence rvalue au constructeur, qui est donc lié au constructeur qui prend une référence rvalue. Cela est évident.
Il y a juste un problème avec ceci; vous n'avez pas demandé de le déplacer. Oh, vous pourriez dire que le &&
aurait dû être un indice, mais cela ne change rien au fait qu'il a enfreint la règle. val
n'est pas temporaire car les temporaires n'ont pas de nom. Vous avez peut-être prolongé la durée de vie du temporaire, mais cela signifie que ce n'est pas temporaire ; c'est comme n'importe quelle autre variable de pile.
Si ce n'est pas temporaire et que vous n'avez pas demandé à le déplacer, alors déplacer est faux.
La solution évidente consiste à définir val
en lvalue. Cela signifie que vous ne pouvez pas en sortir. OK bien; c'est nommé, donc c'est une valeur.
Une fois que vous avez fait cela, vous ne pouvez plus dire que SomeType&&
signifie la même chose partout. Vous avez maintenant fait la distinction entre les références rvalue nommées et les références rvalue non nommées. Eh bien, les références rvalue nommées sont des lvalues; C'était notre solution ci-dessus. Alors, comment appelle-t-on les références rvalue sans nom (la valeur de retour de Func
ci-dessus)?
Ce n'est pas une lvalue, car vous ne pouvez pas passer d'une lvalue. Et nous avons besoin pour pouvoir nous déplacer en renvoyant un &&
; Comment pourriez-vous explicitement dire de déplacer quelque chose? C’est ce que std::move
renvoie, après tout. Ce n'est pas une rvalue (style ancien), parce que cela peut être du côté gauche d'une équation (les choses sont en réalité un peu plus compliquées, voir cette question et les commentaires ci-dessous). Ce n'est ni une valeur ni une valeur; c'est un nouveau genre de chose.
Ce que nous avons est une valeur que vous pouvez traiter comme une lvalue, , sauf à partir de laquelle elle est implicitement déplaçable. Nous appelons cela une valeur x.
Notez que les valeurs x sont ce qui nous fait gagner les deux autres catégories de valeurs:
Une valeur est en réalité simplement le nouveau nom du type de valeur précédent, c’est-à-dire les valeurs qui ne sont pas des valeurs x.
Les glvalues sont l'union de xvalues et de lvalues dans un groupe, car elles partagent un grand nombre de propriétés communes.
Donc, vraiment, tout se résume à xvalues et à la nécessité de restreindre le mouvement à des endroits précis et seulement. Ces lieux sont définis par la catégorie rvalue; prvalues sont les déplacements implicites et xvalues sont les déplacements explicites (std::move
renvoie une valeur x).
IMHO, la meilleure explication sur sa signification nous a donné Stroustrup + prendre en compte des exemples de Dániel Sándor et Mohan :
Stroustrup:
Maintenant, j'étais sérieusement inquiet. Clairement, nous nous dirigions vers une impasse ou un gâchis ou les deux. J'ai passé la pause déjeuner à faire une analyse pour voir quelles propriétés (de valeurs) étaient indépendantes. Il n'y avait que deux propriétés indépendantes:
has identity
- c’est-à-dire une adresse, un pointeur, l’utilisateur peut déterminer si deux copies sont identiques, etc.can be moved from
- c’est-à-dire que nous sommes autorisés à laisser à la source une "copie" dans un état indéterminé, mais valideCela m'a amené à la conclusion qu'il existe exactement trois types de valeurs (en utilisant le truc de notation regex consistant à utiliser une majuscule pour indiquer un négatif - j'étais pressé):
iM
name__: a une identité et ne peut pas être déplacé deim
name__: a une identité et peut être déplacé de (par exemple, le résultat de la conversion d'une valeur lvalue vers une référence rvalue)
Im
name__: n'a pas d'identité et peut être déplacé de.La quatrième possibilité,
IM
name__, (n’a pas d’identité et ne peut pas être déplacée) n’est pas utile dansC++
(ni, je pense, dans une autre langue).Outre ces trois classifications fondamentales de valeurs, nous avons deux généralisations évidentes qui correspondent aux deux propriétés indépendantes:
i
name__: a une identitém
name__: peut être déplacé deCela m’a amené à mettre ce diagramme au tableau:
Appellation
J'ai remarqué que nous n'avions qu'une liberté de nommer limitée: les deux points à gauche (nommés
iM
eti
name__) correspondent à ce que les personnes ayant plus ou moins de formalités ont appelélvalues
et les deux points à droite (nommésm
etIm
name__) avec plus ou moins de formalités ont appelérvalues
name__. Cela doit être reflété dans notre nom. C'est-à-dire que la "jambe" gauche duW
devrait avoir des noms liés àlvalue
et que la "jambe" droite duW
devrait avoir des noms liés àrvalue.
. . Ces notions n’existent tout simplement pas dans le monde de Strachey composé uniquement dervalues
et delvalues
name__. Quelqu'un a observé que les idées qui
- Chaque
value
est soit unlvalue
ou unrvalue
name__lvalue
n'est pas unrvalue
et unrvalue
n'est pas unlvalue
name__sont profondément ancrés dans notre conscience, des propriétés très utiles et des traces de cette dichotomie se retrouvent partout dans le projet de norme. Nous avons tous convenu que nous devrions préserver ces propriétés (et les préciser). Cela a davantage limité nos choix de nommage. J'ai observé que le libellé de la bibliothèque standard utilise
rvalue
pour signifierm
(la généralisation), de sorte que pour conserver l'espérance et le texte de la bibliothèque standard, le point bas de droite deW
devrait être nommérvalue.
Cela a conduit à une discussion ciblée sur la dénomination. Tout d'abord, nous devions choisir
lvalue.
Alvalue
devrait-il signifieriM
ou la généralisationi
name__? Sous la direction de Doug Gregor, nous avons répertorié les endroits dans le langage de base où Wordlvalue
était qualifié pour signifier l'un ou l'autre. Une liste a été faite et dans la plupart des cas et dans le texte le plus compliqué/fragilelvalue
signifie actuellementiM
name__. C’est le sens classique de lvalue car "jadis", rien n’était déplacé;move
est une nouvelle notion dansC++0x
. De plus, nommer le point le plus haut deW
name__lvalue
nous donne la propriété que chaque valeur est unlvalue
ou unrvalue
name__, mais pas les deux.Ainsi, le point en haut à gauche de
W
estlvalue
et le point en bas à droite estrvalue.
. Qu'est-ce que cela fait en bas à gauche et en haut à droite? Le point en bas à gauche est une généralisation de la valeur classique, permettant un déplacement. Donc, c'est ungeneralized lvalue.
Nous l'avons nomméglvalue.
Vous pouvez interroger l'abréviation, mais (je pense) pas avec la logique. Nous avons supposé qu'en cas d'utilisation sérieuse,generalized lvalue
serait en quelque sorte abrégé de toute façon, aussi ferions-nous mieux de le faire immédiatement (sinon nous risquerions une confusion). Le point en haut à droite du W est moins général que le coin en bas à droite (maintenant, comme toujours, appelérvalue
name__). Ce point représente la notion pure originale d'objet à partir duquel vous pouvez vous déplacer, car il ne peut plus y être fait référence (sauf par un destructeur). J'ai aimé l'expressionspecialized rvalue
par opposition àgeneralized lvalue
maispure rvalue
en abrégéprvalue
a été jugée gagnante (et probablement à juste titre). Ainsi, la jambe gauche du W estlvalue
etglvalue
et la jambe droite estprvalue
etrvalue.
Incidemment, chaque valeur est une valeur glvalue ou prvalue, mais pas les deux.Cela laisse la partie supérieure supérieure du
W
name__:im
name__; c'est-à-dire des valeurs qui ont une identité et qui peuvent être déplacées. Nous n’avons vraiment rien qui nous guide vers un bon nom pour ces bêtes ésotériques. Ils sont importants pour les personnes qui travaillent avec le projet de texte standard, mais il est peu probable qu'ils deviennent un nom familier. Nous n’avons trouvé aucune contrainte réelle sur la dénomination pour nous guider, nous avons donc choisi "x" pour le centre, l’inconnu, l’étrange, le xpert uniquement, voire même le x-rated.
ISOC++ 11 (officiellement ISO/IEC 14882: 2011) est la version la plus récente du standard du langage de programmation C++. Il contient quelques nouvelles fonctionnalités et concepts, par exemple:
Si nous souhaitons comprendre les concepts des nouvelles catégories de valeur d'expression, nous devons savoir qu'il existe des références rvalue et lvalue. Il est préférable de savoir que les valeurs peuvent être transmises à des références non constantes.
int& r_i=7; // compile error
int&& rr_i=7; // OK
Nous pouvons avoir une idée des concepts de catégories de valeur en citant la sous-section intitulée Lvalues et rvalues du brouillon N3337 (le brouillon le plus semblable à la norme publiée ISOC++ 11).
3.10 Lvalues et rvalues [basic.lval]
1 Les expressions sont classées selon la taxonomie de la figure 1.
- Une lvalue (ainsi appelée historiquement, car des valeurs pourraient apparaître à gauche d'une expression d'affectation) désigne une fonction ou un objet. [Exemple: Si E est une expression de type pointeur, alors * E est une expression lvalue faisant référence à l'objet ou à la fonction sur laquelle E pointe. Autre exemple, l'appel d'une fonction dont le type de retour est une référence à lvalue est une lvalue. —Fin exemple]
- Une valeur x (une valeur "expirante") fait également référence à un objet, généralement vers la fin de sa vie (pour que ses ressources puissent être déplacées, par exemple). Une valeur x est le résultat de certains types d'expressions impliquant des références rvalue (8.3.2). [Exemple: le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une valeur x. —Fin exemple]
- Une glvalue (lvalue "généralisée") est une lvalue ou une xvalue.
- Une valeur (appelée ainsi, historiquement, car des valeurs peuvent apparaître à droite d'une expression d'affectation) est une valeur x, une valeur
objet temporaire (12.2) ou sous-objet de celui-ci, ou une valeur qui n'est pas
associé à un objet.- Une valeur (valeur "pure") est une valeur qui n'est pas une valeur x. [Exemple: résultat de l’appel d’une fonction dont le type de retour n’est pas un
référence est une valeur. La valeur d'un littéral tel que 12, 7.3e5 ou
true est également une valeur. —Fin exemple]Chaque expression appartient exactement à l'une des classifications fondamentales de cette taxonomie: lvalue, xvalue ou prvalue. Cette propriété d'une expression s'appelle sa catégorie de valeur.
Mais je ne suis pas tout à fait sûr que cette sous-section soit suffisante pour bien comprendre les concepts, car "généralement" n’est pas vraiment général, "sa fin de vie" n’est pas vraiment concret, "impliquant des références rvalue" n’est pas vraiment clair, et "Exemple: le résultat de l'appel d'une fonction dont le type de retour est une référence rvalue est une valeur x." sonne comme un serpent se mord la queue.
Chaque expression appartient à une seule catégorie de valeur primaire. Ces catégories de valeur sont les catégories lvalue, xvalue et prvalue.
L'expression E appartient à la catégorie lvalue si et seulement si E fait référence à une entité pour laquelle ALREADY a déjà une identité (adresse, nom ou pseudonyme) la rendant accessible en dehors de E.
#include <iostream>
int i=7;
const int& f(){
return i;
}
int main()
{
std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.
i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression i in this row refers to.
int* p_i=new int(7);
*p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
*p_i; // ... as the entity the expression *p_i in this row refers to.
const int& r_I=7;
r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
r_I; // ... as the entity the expression r_I in this row refers to.
f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression f() in this row refers to.
return 0;
}
L'expression E appartient à la catégorie xvalue si et seulement si c'est
- le résultat de l'appel d'une fonction, implicite ou explicite, dont le type de retour est une référence rvalue au type d'objet renvoyé, o
int&& f(){
return 3;
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.
return 0;
}
- une conversion sur une référence rvalue vers un type d'objet, o
int main()
{
static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).
return 0;
}
- une expression d'accès de membre de classe désignant un membre de données non statique de type non référence dans lequel l'expression d'objet est une valeur xvalue, o
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.
return 0;
}
- une expression de pointeur à membre dans laquelle le premier opérande est une valeur x et le deuxième opérande est un pointeur sur un membre de données.
Notez que les règles ci-dessus ont pour effet que les références rvalue nommées aux objets sont traitées comme des lvalues et que les références rvalue non nommées aux objets sont traitées comme des xvalues; Les références rvalue aux fonctions sont traitées comme des lvalues, qu'elles soient nommées ou non.
#include <functional>
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
As&& rr_a=As();
rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.
return 0;
}
L'expression E appartient à la catégorie prvalue si et seulement si E n'appartient ni à la lvalue ni à la catégorie xvalue.
struct As
{
void f(){
this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
}
};
As f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.
return 0;
}
Il existe deux autres catégories de valeurs mixtes importantes. Ces catégories de valeur sont les catégories rvalue et glvalue.
L'expression E appartient à la catégorie rvalue si et seulement si E appartient à la catégorie xvalue ou à la catégorie prvalue.
Notez que cette définition signifie que l'expression E appartient à la catégorie rvalue si et seulement si E fait référence à une entité qui n'a aucune identité la rendant accessible en dehors de E YET.
L'expression E appartient à la catégorie glvalue si et seulement si E appartient à la catégorie lvalue ou à la catégorie xvalue.
Scott Meyer a publié une règle très utile pour distinguer les valeurs des valeurs.
- Si vous pouvez prendre l'adresse d'une expression, celle-ci est une lvalue.
- Si le type d'une expression est une référence de valeur (par exemple, T & ou const T &, etc.), cette expression est une valeur.
- Sinon, l'expression est une valeur. Conceptuellement (et généralement aussi en fait), les rvalues correspondent à des objets temporaires, tels que ceux renvoyés à partir de fonctions ou créés par le biais de conversions de types implicites. La plupart des valeurs littérales (par exemple, 10 et 5.3) sont également des valeurs.
Les catégories de C++ 03 sont trop limitées pour capturer correctement l'introduction de références rvalue dans les attributs d'expression.
Lors de leur introduction, il a été dit qu'une référence de valeur non nommée est évaluée à une valeur de telle sorte que la résolution de surcharge préfère les liaisons de référence de valeur, ce qui l'obligerait à sélectionner des constructeurs de déplacement par rapport aux constructeurs de copie. Mais il a été constaté que cela posait des problèmes, par exemple avec types dynamiques et avec des qualifications.
Pour montrer cela, considérons
int const&& f();
int main() {
int &&i = f(); // disgusting!
}
Cela a été autorisé pour les brouillons pré-xvalue, car en C++ 03, les valeurs de type non-classe ne sont jamais qualifiées de cv Mais il est prévu que const
s'applique dans le cas rvalue-reference, car ici nous faisons nous référons à des objets (= memory!), Et abandonner const de rvalues de non-classe est principalement destiné à la raison pour laquelle il n'y a pas d'objet autour.
Le problème pour les types dynamiques est de même nature. En C++ 03, les rvalues de type classe ont un type dynamique connu: il s'agit du type statique de cette expression. Parce que pour le faire autrement, vous avez besoin de références ou de déréférences, qui sont évaluées à une valeur. Ce n'est pas vrai avec les références rvalue sans nom, pourtant elles peuvent montrer un comportement polymorphe. Alors pour le résoudre,
les références rvalue non nommées deviennent xvalues . Ils peuvent être qualifiés et potentiellement avoir leur type dynamique différent. Comme prévu, ils préfèrent les références rvalue lors de la surcharge et ne se lient pas aux références non constantes.
Ce qui était auparavant une rvalue (littéraux, objets créés par des conversions vers des types non référencés) devient maintenant une prvalue . Ils ont la même préférence que xvalues lors d'une surcharge.
Ce qui était auparavant une lvalue reste une lvalue.
Et deux regroupements sont effectués pour capturer ceux qui peuvent être qualifiés et peuvent avoir différents types dynamiques ( glvalues ) et ceux pour lesquels la surcharge préfère la liaison de référence rvalue ( rvalues ).
Cela fait longtemps que je lutte avec cela, jusqu'à ce que je tombe sur l'explication de cppreference.com de catégories de valeur .
C'est en fait assez simple, mais je trouve que cela est souvent expliqué de manière difficile à mémoriser. Ici, il est expliqué très schématiquement. Je citerai quelques parties de la page:
Catégories primaires
Les catégories de valeurs primaires correspondent à deux propriétés d'expressions:
a une identité : il est possible de déterminer si l'expression fait référence à la même entité qu'une autre expression, par exemple en comparant les adresses des objets ou les fonctions identifiées. (obtenus directement ou indirectement);
peut être déplacé de : constructeur de déplacement, opérateur d'affectation de déplacement ou toute autre surcharge de fonction implémentant une sémantique de déplacement pouvant être liée à l'expression.
Des expressions qui:
- ont une identité et ne peuvent pas en être déplacés sont appelés lvalue expressions ;
- ont une identité et peuvent être déplacés de sont appelés xvalue expressions ;
- n'ont pas d'identité et peuvent être déplacés depuis sont appelés des expressions de valeur ;
- n'ont pas d'identité et ne peuvent pas être déplacés de ne sont pas utilisés.
lvalue
Une expression lvalue ("valeur de gauche") est une expression qui a une identité et ne peut pas être déplacée de .
rvalue (jusqu'à C++ 11), prvalue (depuis C++ 11)
Une expression prvalue ("pure rvalue") est une expression qui n'a pas d'identité et peut être déplacée de .
xvalue
Une expression xvalue ("valeur d'expiration") est une expression qui a une identité et peut être déplacée de .
glvalue
Une expression glvalue ("generalized lvalue") est une expression qui est une lvalue ou une xvalue. Il a une identité . Il peut ou ne peut pas être déplacé de.
rvalue (depuis C++ 11)
Une expression rvalue ("valeur correcte") est une expression qui est une valeur ou une valeur. Il peut être déplacé de . Il peut ou peut ne pas avoir d'identité.
Quel est le lien entre ces nouvelles catégories et les catégories rvalue et lvalue existantes?
Une valeur C++ 03 est toujours une valeur C++ 11, alors qu'une valeur C++ 03 est appelée une valeur en C++ 11.
Un addendum aux excellentes réponses ci-dessus, sur un point qui m'a confondu même après avoir lu Stroustrup et pensé avoir compris la distinction entre valeur et valeur. Quand tu vois
int&& a = 3
,
il est très tentant de lire le int&&
en tant que type et de conclure que a
est une rvalue. Ce n'est pas:
int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles
a
a un nom et est ipso facto une lvalue. Ne pensez pas que le &&
fait partie du type de a
; c'est simplement quelque chose qui vous dit à quoi a
est autorisé à se lier.
Ceci est particulièrement important pour les arguments de type T&&
dans les constructeurs. Si vous écrivez
Foo::Foo(T&& _t) : t{_t} {}
vous allez copier _t
dans t
. Vous avez besoin
Foo::Foo(T&& _t) : t{std::move(_t)} {}
si vous souhaitez vous déplacer. Mon compilateur m’avait-il averti quand j’aurais oublié le move
!
Comme les réponses précédentes couvraient de manière exhaustive la théorie sous-jacente aux catégories de valeur, j'aimerais ajouter une autre chose: vous pouvez réellement jouer avec et tester.
Pour certaines expériences pratiques avec les catégories de valeur, vous pouvez utiliser le spécificateur decltype . Son comportement distingue explicitement les trois catégories de valeur principales (xvalue, lvalue et prvalue).
L'utilisation du préprocesseur nous évite d'avoir à taper ...
Catégories primaires:
#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value
Catégories mixtes:
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)
Nous pouvons maintenant reproduire (presque) tous les exemples de référence à la catégorie de valeur .
Voici quelques exemples avec C++ 17 (pour terse static_assert):
void doesNothing(){}
struct S
{
int x{0};
};
int x = 1;
int y = 2;
S s;
static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));
static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));
static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x));
Les catégories mixtes sont assez ennuyeuses une fois que vous avez déterminé la catégorie principale.
Pour quelques exemples supplémentaires (et expérimentation), jetez un œil à ce qui suit lien sur l'explorateur du compilateur Ne prenez pas la peine de lire l'Assemblée, cependant. J'ai ajouté de nombreux compilateurs uniquement pour m'assurer que cela fonctionne sur tous les compilateurs courants.