Buscar..


Significados de la categoría de valor

A las expresiones en C ++ se les asigna una categoría de valor particular, en función del resultado de esas expresiones. Las categorías de valor para las expresiones pueden afectar la resolución de sobrecarga de la función C ++.

Las categorías de valor determinan dos propiedades importantes pero separadas de una expresión. Una propiedad es si la expresión tiene identidad. Una expresión tiene identidad si se refiere a un objeto que tiene un nombre de variable. Es posible que el nombre de la variable no esté involucrado en la expresión, pero el objeto aún puede tener uno.

La otra propiedad es si es legal moverse implícitamente del valor de la expresión. O más específicamente, si la expresión, cuando se usa como un parámetro de función, se unirá a los tipos de parámetros de valor r o no.

C ++ define 3 categorías de valores que representan la combinación útil de estas propiedades: lvalue (expresiones con identidad pero que no se pueden mover desde), xvalue (expresiones con identidad que se pueden mover desde), y prvalue (expresiones sin identidad que se pueden mover desde). C ++ no tiene expresiones que no tengan identidad y no puedan moverse.

C ++ define otras dos categorías de valores, cada una basada únicamente en una de estas propiedades: glvalue (expresiones con identidad) y rvalue (expresiones desde las que se puede mover). Estos actúan como agrupaciones útiles de las categorías anteriores.

Este gráfico sirve como ilustración:

Gráfica de categorías de valor en el lenguaje C ++

prvalue

Una expresión prvalue (pure-rvalue) es una expresión que carece de identidad, cuya evaluación se usa normalmente para inicializar un objeto y que se puede mover de forma implícita. Estos incluyen, pero no se limitan a:

  • Expresiones que representan objetos temporales, como std::string("123") .
  • Una expresión de llamada de función que no devuelve una referencia.
  • Un literal ( excepto un literal de cadena - esos son valores de l), como tiene 1 , true , 0.5f , o 'a'
  • Una expresión lambda

La dirección incorporada del operador ( & ) no se puede aplicar a estas expresiones.

xvalor

Una expresión xvalue (valor eXpiring) es una expresión que tiene identidad y representa un objeto desde el cual se puede mover implícitamente. La idea general con las expresiones xvalue es que el objeto que representan se destruirá pronto (de ahí la parte "inspiradora"), y por lo tanto, mudarse de forma implícita está bien.

Dado:

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

valor

Una expresión lvalue es una expresión que tiene identidad, pero no se puede mover implícitamente. Entre ellas se encuentran las expresiones que consisten en un nombre de variable, un nombre de función, expresiones que son usos de operadores de desreferencia incorporados y expresiones que se refieren a referencias de valores.

El lvalue típico es simplemente un nombre, pero los lvalues ​​también pueden venir en otros sabores:

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

Además, mientras que la mayoría de los literales (por ejemplo, 4 , 'x' , etc.) son prvalores, los literales de cadenas son valores l.

glvalue

Una expresión glvalue (un "lvalue generalizado") es cualquier expresión que tiene identidad, independientemente de si se puede mover o no. Esta categoría incluye lvalues ​​(expresiones que tienen identidad pero no se pueden mover) y xvalues ​​(expresiones que tienen identidad y se pueden mover desde), pero excluye prvalues ​​(expresiones sin identidad).

Si una expresión tiene un nombre , es un 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

valor

Una expresión de rvalor es cualquier expresión a la que se puede mover implícitamente, independientemente de si tiene identidad.

Más precisamente, las expresiones rvalue se pueden usar como argumento para una función que toma un parámetro de tipo T && (donde T es el tipo de expr ). Solo se pueden dar expresiones de valor como argumentos a tales parámetros de función; Si se usa una expresión sin valor, la resolución de sobrecarga seleccionará cualquier función que no use un parámetro de referencia de valor. Y si no existe ninguno, entonces obtienes un error.

La categoría de expresiones rvalue incluye todas las expresiones xvalue y prvalue, y solo esas expresiones.

La función de biblioteca estándar std::move existe para transformar explícitamente una expresión sin valor en un valor. Más específicamente, convierte la expresión en un xvalor, ya que incluso si antes era una expresión de prvalor sin identidad, al pasarla como parámetro a std::move , gana identidad (el nombre del parámetro de la función) y se convierte en un xvalor.

Considera lo siguiente:

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 tiene un constructor que toma un solo parámetro de tipo std::string&& , comúnmente llamado "constructor de movimiento". Sin embargo, la categoría de valor de la expresión str no es un rvalue (específicamente es un lvalue), por lo que no puede llamar a esa sobrecarga del constructor. En su lugar, llama a const std::string& overload, el constructor de copia.

La línea 3 cambia las cosas. El valor de retorno de std::move es un T&& , donde T es el tipo base del parámetro pasado. Así que std::move(str) devuelve std::string&& . Una llamada de función cuyo valor de retorno es una referencia rvalue es una expresión rvalue (específicamente un valor x), por lo que puede llamar al constructor de movimiento de std::string . Después de la línea 3, str se ha movido desde (los contenidos de quién ahora están indefinidos).

La línea 4 pasa un operador temporal al operador de asignación de std::string . Esto tiene una sobrecarga que lleva un std::string&& . La expresión std::string("new value") es una expresión rvalue (específicamente un prvalue), por lo que puede llamar a esa sobrecarga. Por lo tanto, el temporal se mueve a str , reemplazando los contenidos indefinidos con contenidos específicos.

La línea 5 crea una referencia str_ref llamada str_ref que se refiere a str . Aquí es donde las categorías de valor se vuelven confusas.

Vea, mientras que str_ref es una referencia de rvalue a std::string , la categoría de valor de la expresión str_ref no es un valor de r . Es una expresión lvalue. Sí, en serio. Debido a esto, no se puede llamar al constructor de movimiento de std::string con la expresión str_ref . La línea 6, por lo tanto, copia el valor de str en test3 .

Para moverlo, tendríamos que emplear std::move nuevamente.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow