Recherche…


Signification des catégories de valeur

Les expressions en C ++ se voient attribuer une catégorie de valeur particulière, en fonction du résultat de ces expressions. Les catégories de valeur pour les expressions peuvent affecter la résolution de la surcharge de la fonction C ++.

Les catégories de valeur déterminent deux propriétés importantes mais distinctes d'une expression. Une propriété est de savoir si l'expression a une identité. Une expression a une identité si elle fait référence à un objet qui a un nom de variable. Le nom de la variable peut ne pas être impliqué dans l'expression, mais l'objet peut toujours en avoir un.

L'autre propriété est de savoir s'il est légal de déplacer implicitement la valeur de l'expression. Ou plus précisément, si l'expression, utilisée en tant que paramètre de fonction, sera liée aux types de paramètres de valeur r ou non.

C ++ définit 3 catégories de valeurs qui représentent la combinaison utile de ces propriétés: lvalue (expressions avec identité mais non déplaçables à partir de), xvalue (expressions avec identité pouvant être déplacées) et prvalue (expressions sans identité à partir desquelles). C ++ n'a pas d'expressions sans identité et ne peut pas être déplacé.

C ++ définit deux autres catégories de valeurs, chacune basée uniquement sur l'une de ces propriétés: glvalue (expressions avec identité) et rvalue (expressions pouvant être déplacées). Ceux-ci agissent comme des regroupements utiles des catégories précédentes.

Ce graphique sert d'illustration:

Graphique des catégories de valeurs dans le langage C ++

valeur

Une expression prvalue (pure-rvalue) est une expression dépourvue d'identité, dont l'évaluation est généralement utilisée pour initialiser un objet, et qui peut être implicitement déplacée. Ceux-ci incluent, mais ne sont pas limités à:

  • Expressions représentant des objets temporaires, tels que std::string("123") .
  • Une expression d'appel de fonction qui ne renvoie pas de référence
  • Un littéral ( sauf un littéral de chaîne - ce sont des lvalues), tel que 1 , true , 0.5f ou 'a'
  • Une expression lambda

L'adresse intégrée de l'opérateur ( & ) ne peut pas être appliquée à ces expressions.

xvalue

Une expression xvalue (eXpiring value) est une expression ayant une identité et représentant un objet pouvant être implicitement déplacé. L'idée générale des expressions xvalue est que l'objet qu'elles représentent va bientôt être détruit (d'où la partie "eXpiring"), et par conséquent, se déplacer implicitement de ces objets est correct.

Donné:

struct X { int n; };
extern X x;

4;                   // prvalue: does not have an identity
x;                   // lvalue
x.n;                 // lvalue
std::move(x);        // xvalue
std::forward<X&>(x); // lvalue
X{4};                // prvalue: does not have an identity
X{4}.n;              // xvalue: does have an identity and denotes resources
                     // that can be reused

lvalue

Une expression lvalue est une expression qui a une identité, mais ne peut pas être implicitement déplacée. Parmi celles-ci figurent des expressions composées d'un nom de variable, d'un nom de fonction, d'expressions utilisées par l'opérateur de déréférencement intégré et d'expressions faisant référence à des références lvalue.

La lvalue typique est simplement un nom, mais les lvalues ​​peuvent également avoir d'autres saveurs:

struct X { ... };

X x;         // x is an lvalue
X* px = &x;  // px is an lvalue
*px = X{};   // *px is also an lvalue, X{} is a prvalue

X* foo_ptr();  // foo_ptr() is a prvalue
X& foo_ref();  // foo_ref() is an lvalue

De plus, alors que la plupart des littéraux (par exemple 4 , 'x' , etc.) sont des valeurs, les littéraux de chaîne sont des lvalues.

glvalue

Une expression glvalue (une "lvalue généralisée") est une expression qui a une identité, qu'elle puisse ou non être déplacée. Cette catégorie inclut les lvalues ​​(expressions qui ont une identité mais qui ne peuvent pas être déplacées) et les xvalues ​​(expressions qui ont une identité et peuvent être déplacées), mais exclut les valeurs (expressions sans identité).

Si une expression a un nom , c'est une glvalue:

struct X { int n; };
X foo();

X x;
x; // has a name, so it's a glvalue
std::move(x); // has a name (we're moving from "x"), so it's a glvalue
              // can be moved from, so it's an xvalue not an lvalue

foo(); // has no name, so is a prvalue, not a glvalue
X{};   // temporary has no name, so is a prvalue, not a glvalue
X{}.n; // HAS a name, so is a glvalue. can be moved from, so it's an xvalue

rvalue

Une expression rvalue est une expression pouvant être implicitement déplacée, qu'elle ait ou non une identité.

Plus précisément, les expressions rvalue peuvent être utilisées comme argument d'une fonction qui prend un paramètre de type T && (où T est le type de expr ). Seules les expressions rvalue peuvent être utilisées comme arguments pour ces paramètres de fonction; Si une expression non-rvalue est utilisée, alors la résolution de la surcharge choisira toute fonction n'utilisant pas de paramètre de référence rvalue. Et si aucun n'existe, alors vous obtenez une erreur.

La catégorie des expressions rvalue comprend toutes les expressions xvalue et prvalue, et uniquement ces expressions.

La fonction standard de la bibliothèque std::move existe pour transformer explicitement une expression non-rvalue en une rvalue. Plus précisément, il transforme l'expression en une xvalue, car même si c'était une expression de valeur sans identité auparavant, en la passant en paramètre à std::move , elle acquiert une identité (le nom du paramètre de la fonction) et devient une valeur x.

Considérer ce qui suit:

std::string str("init");                       //1
std::string test1(str);                        //2
std::string test2(std::move(str));             //3

str = std::string("new value");                //4 
std::string &&str_ref = std::move(str);        //5
std::string test3(str_ref);                    //6

std::string a un constructeur qui prend un seul paramètre de type std::string&& , communément appelé "constructeur de déplacement". Cependant, la catégorie de valeur de l'expression str n'est pas une valeur (en particulier, c'est une lvalue), elle ne peut donc pas appeler cette surcharge du constructeur. Au lieu de cela, il appelle le const std::string& overload, le constructeur de la copie.

La ligne 3 change les choses. La valeur de retour de std::move est un T&& , où T est le type de base du paramètre passé. Donc std::move(str) renvoie std::string&& . Un appel de fonction dont la valeur de retour est une référence rvalue est une expression rvalue (spécifiquement une xvalue), de sorte qu'il peut appeler le constructeur move de std::string . Après la ligne 3, str a été déplacé de (dont le contenu est maintenant indéfini).

La ligne 4 transmet un temporaire à l'opérateur d'affectation de std::string . Cela a une surcharge qui prend un std::string&& . L'expression std::string("new value") est une expression rvalue (spécifiquement une valeur), elle peut donc appeler cette surcharge. Ainsi, le temporaire est déplacé dans str , remplaçant le contenu non défini par un contenu spécifique.

La ligne 5 crée une référence nommée str_ref appelée str_ref qui fait référence à str . C'est là que les catégories de valeur sont déroutantes.

Voir, alors que str_ref est une référence rvalue à std::string , la catégorie de valeur de l'expression str_ref n'est pas une rvalue . C'est une expression de lvalue. Oui vraiment. De ce fait, on ne peut pas appeler le constructeur move de std::string avec l'expression str_ref . La ligne 6 copie donc la valeur de str dans test3 .

Pour le déplacer, il faudrait utiliser std::move nouveau.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow