En C ++ 03, una expresión es un valor r o un valor l .
En C ++ 11, una expresión puede ser un:
- rvalue
- lvalue
- xvalue
- glvalue
- prvalue
Dos categorías se han convertido en cinco categorías.
- ¿Cuáles son estas nuevas categorías de expresiones?
- ¿Cómo se relacionan estas nuevas categorías con las categorías rvalue y lvalue existentes?
- ¿Son las categorías rvalue y lvalue en C ++ 0x las mismas que en C ++ 03?
- ¿Por qué se necesitan estas nuevas categorías? ¿Están los dioses del WG21 tratando de confundirnos como simples mortales?
c++
expression
c++-faq
c++11
James McNellis
fuente
fuente
string("hello") = string("world")
.Respuestas:
Supongo que este documento podría servir como una introducción no tan corta: n3055
Toda la masacre comenzó con la semántica del movimiento. Una vez que tenemos expresiones que se pueden mover y no copiar, de repente las reglas fáciles de entender exigieron la distinción entre expresiones que se pueden mover y en qué dirección.
Por lo que supongo basado en el borrador, la distinción del valor r / l permanece igual, solo en el contexto de mover las cosas se vuelven desordenadas.
¿Son necesarios? Probablemente no si deseamos perder las nuevas funciones. Pero para permitir una mejor optimización, probablemente deberíamos adoptarlos.
Citando n3055 :
E
es una expresión de tipo puntero, entonces*E
es una expresión de valor l que se refiere al objeto o función a la queE
apunta. Como otro ejemplo, el resultado de llamar a una función cuyo tipo de retorno es una referencia de lvalue es un lvalue.]El documento en cuestión es una gran referencia para esta pregunta, ya que muestra los cambios exactos en el estándar que han sucedido como resultado de la introducción de la nueva nomenclatura.
fuente
El FCD (n3092) tiene una excelente descripción:
Sin embargo, le sugiero que lea toda la sección 3.10 Lvalues and rvalues .
De nuevo:
La semántica de los valores ha evolucionado particularmente con la introducción de la semántica de movimiento.
Para que la construcción / asignación de movimiento se pueda definir y apoyar.
fuente
glvalue
aslvalue
ylvalue
asplvalue
, para ser coherente?Comenzaré con tu última pregunta:
El estándar C ++ contiene muchas reglas que tratan con la categoría de valor de una expresión. Algunas reglas hacen una distinción entre lvalue y rvalue. Por ejemplo, cuando se trata de resolución de sobrecarga. Otras reglas hacen una distinción entre glvalue y prvalue. Por ejemplo, puede tener un valor de gl con un tipo incompleto o abstracto, pero no hay ningún valor con un tipo de resumen o incompleto. Antes de tener esta terminología, las reglas que realmente necesitaban distinguir entre glvalue / prvalue se referían a lvalue / rvalue y estaban equivocadas involuntariamente o contenían muchas explicaciones y excepciones a la regla a la "... a menos que el rvalue se deba a un nombre sin nombre rvalue reference ... ". Por lo tanto, parece una buena idea dar a los conceptos de valores y valores su propio nombre.
Todavía tenemos los términos lvalue y rvalue que son compatibles con C ++ 98. Acabamos de dividir los valores en dos subgrupos, valores x y valores, y nos referimos a valores y valores x como valores. Los valores X son un nuevo tipo de categoría de valor para referencias de valor sin nombre. Cada expresión es uno de estos tres: lvalue, xvalue, prvalue. Un diagrama de Venn se vería así:
Ejemplos con funciones:
Pero tampoco olvide que las referencias rvalue nombradas son valores:
fuente
No creo que las otras respuestas (bueno, aunque muchas de ellas lo sean) realmente capturen la respuesta a esta pregunta en particular. Sí, estas categorías y otras existen para permitir la semántica de movimiento, pero la complejidad existe por una razón. Esta es la única regla inviolable de mover cosas en C ++ 11:
Solo te moverás cuando sea indudablemente seguro hacerlo.
Es por eso que existen estas categorías: para poder hablar sobre valores donde es seguro moverse de ellos, y para hablar sobre valores donde no es seguro.
En la primera versión de las referencias de valor r, el movimiento sucedió fácilmente. Demasiado fácil Es bastante fácil que hubiera mucho potencial para mover cosas implícitamente cuando el usuario realmente no quería hacerlo.
Estas son las circunstancias bajo las cuales es seguro mover algo:
Si haces esto:
¿Qué hace esto? En versiones anteriores de la especificación, antes de que entraran los 5 valores, esto provocaría un movimiento. Claro que lo hace. Pasó una referencia de valor de r al constructor y, por lo tanto, se une al constructor que toma una referencia de valor de r. Eso es obvio.
Solo hay un problema con esto; No pediste moverlo. Oh, podrías decir que
&&
debería haber sido una pista, pero eso no cambia el hecho de que rompió la regla.val
no es temporal porque los temporales no tienen nombres. Es posible que haya extendido la vida útil de lo temporal, pero eso significa que no lo es temporal ; Es como cualquier otra variable de pila.Si no es temporal y no solicitó moverlo, entonces mover es incorrecto.
La solución obvia es hacer
val
un valor l. Esto significa que no puedes moverte de él. Está bien; se llama así que es un valor.Una vez que haces eso, ya no puedes decir que eso
SomeType&&
significa lo mismo en todas partes. Ahora ha hecho una distinción entre las referencias de rvalue con nombre y las referencias de rvalue sin nombre. Bueno, las referencias rvalue nombradas son lvalues; esa fue nuestra solución anterior. Entonces, ¿qué llamamos referencias de valor sin nombre (el valor de retorno deFunc
arriba)?No es un valor, porque no puedes moverte de un valor. Y necesitamos poder movernos devolviendo a
&&
; ¿De qué otra forma podrías decir explícitamente que muevas algo? Eso es lo questd::move
vuelve, después de todo. No es un valor r (estilo antiguo), porque puede estar en el lado izquierdo de una ecuación (las cosas son en realidad un poco más complicadas, vea esta pregunta y los comentarios a continuación). No es ni un valor ni un valor; Es un nuevo tipo de cosas.Lo que tenemos es un valor que puede tratar como un valor l, excepto que es implícitamente movible desde. Lo llamamos un xvalue.
Tenga en cuenta que los valores x son los que nos hacen ganar las otras dos categorías de valores:
Un prvalue es realmente el nuevo nombre para el tipo de rvalue anterior, es decir, son los valores que no son xvalues.
Los valores son la unión de valores x y valores en un grupo, porque comparten muchas propiedades en común.
Entonces, en realidad, todo se reduce a los valores x y la necesidad de restringir el movimiento a lugares específicos y solo a ellos. Esos lugares están definidos por la categoría rvalue; los valores son los movimientos implícitos, y los valores x son los movimientos explícitos (
std::move
devuelve un valor x).fuente
&&
.X foo(); foo() = X;
... Por esta razón fundamental, no puedo seguir la excelente respuesta anterior hasta el final, porque realmente solo haces la distinción entre el nuevo xvalue, y el antiguo estilo prvalue, basado en el hecho de que puede estar en lhs.X
ser una clase;X foo();
ser una declaración de función yfoo() = X();
ser una línea de código. (Dejé el segundo conjunto de paréntesis enfoo() = X();
mi comentario anterior). Para una pregunta que acabo de publicar con este uso resaltado, consulte stackoverflow.com/questions/15482508/…En mi humilde opinión, la mejor explicación sobre su significado nos dio Stroustrup + tener en cuenta ejemplos de Dániel Sándor y Mohan :
Stroustrup:
fuente
lvalue
s, todos los demás literales sonprvalue
s. Estrictamente hablando, podría argumentar que los literales que no son cadenas deben ser inamovibles, pero no es así como se escribe el estándar.INTRODUCCIÓN
ISOC ++ 11 (oficialmente ISO / IEC 14882: 2011) es la versión más reciente del estándar del lenguaje de programación C ++. Contiene algunas características y conceptos nuevos, por ejemplo:
Si quisiéramos comprender los conceptos de las nuevas categorías de valores de expresión, debemos ser conscientes de que existen referencias de valor y valor. Es mejor saber que los valores se pueden pasar a referencias de valor no constantes.
Podemos obtener cierta intuición de los conceptos de categorías de valores si citamos la subsección titulada Lvalues and rvalues del borrador de trabajo N3337 (el borrador más similar al estándar ISOC ++ 11 publicado).
Pero no estoy muy seguro de que esta subsección sea suficiente para comprender los conceptos claramente, porque "por lo general" no es realmente general, "cerca del final de su vida útil" no es realmente concreto, "involucrar referencias de valor" no es realmente claro, y "Ejemplo: el resultado de llamar a una función cuyo tipo de retorno es una referencia rvalue es un xvalue". Suena como una serpiente mordiéndose la cola.
CATEGORÍAS DE VALOR PRIMARIO
Cada expresión pertenece exactamente a una categoría de valor primario. Estas categorías de valor son lvalue, xvalue y prvalue.
lvalues
La expresión E pertenece a la categoría lvalue si y solo si E se refiere a una entidad que YA ha tenido una identidad (dirección, nombre o alias) que la hace accesible fuera de E.
xvalores
La expresión E pertenece a la categoría xvalue si y solo si es
- el resultado de llamar a una función, ya sea implícita o explícitamente, cuyo tipo de retorno es una referencia de valor al tipo de objeto que se devuelve, o
- una conversión a una referencia rvalue al tipo de objeto, o
- una expresión de acceso de miembro de clase que designa un miembro de datos no estático de tipo sin referencia en el que la expresión de objeto es un valor x, o
- una expresión de puntero a miembro en la que el primer operando es un valor xy el segundo operando es un puntero al miembro de datos.
Tenga en cuenta que el efecto de las reglas anteriores es que las referencias de valor r con nombre a los objetos se tratan como valores y las referencias de valor r sin nombre a los objetos se tratan como valores x; Las referencias de rvalue a las funciones se tratan como valores ya sean nombrados o no.
prvalues
La expresión E pertenece a la categoría prvalue si y solo si E no pertenece ni al lvalue ni a la categoría xvalue.
CATEGORÍAS DE VALOR MIXTO
Hay otras dos categorías importantes de valor mixto. Estas categorías de valor son rvalue y glvalue.
valores
La expresión E pertenece a la categoría rvalue si y solo si E pertenece a la categoría xvalue, o a la categoría prvalue.
Tenga en cuenta que esta definición significa que la expresión E pertenece a la categoría rvalue si y solo si E se refiere a una entidad que no ha tenido ninguna identidad que la haga accesible fuera de E YET.
valores
La expresión E pertenece a la categoría glvalue si y solo si E pertenece a la categoría lvalue, o a la categoría xvalue.
UNA REGLA PRÁCTICA
Scott Meyer ha publicado una regla práctica muy útil para distinguir los valores de los valores.
fuente
struct As{void f(){this;}}
lathis
variable es un valor. Pensé quethis
debería ser un valor. Hasta que el estándar 9.3.2 diga: En el cuerpo de una función miembro no estática (9.3), la palabra clave es una expresión prvalue.this
es un prvalue pero*this
es un"www"
No siempre tiene la misma dirección. Es un valor porque es una matriz .Las categorías de C ++ 03 están demasiado restringidas para capturar la introducción de referencias rvalue correctamente en los atributos de expresión.
Con la introducción de ellos, se dijo que una referencia de valor de r sin nombre se evalúa como un valor de r, de modo que la resolución de sobrecarga preferiría enlaces de referencia de valor de r, lo que lo haría seleccionar constructores de movimiento sobre constructores de copia. Pero se descubrió que esto causa problemas por todas partes, por ejemplo con Tipos dinámicos y con calificaciones.
Para mostrar esto, considere
En los borradores anteriores al valor x, esto estaba permitido, porque en C ++ 03, los valores de los tipos que no son de clase nunca tienen calificación cv. Pero se pretende que se
const
aplique en el caso rvalue-reference, porque aquí lo hacemos referencia a objetos (= memoria!), Y dejando caer const de rvalues no clase es principalmente por la razón de que no hay ningún objeto alrededor.El problema para los tipos dinámicos es de naturaleza similar. En C ++ 03, los valores de tipo de clase tienen un tipo dinámico conocido: es el tipo estático de esa expresión. Porque para tenerlo de otra manera, necesita referencias o desreferencias, que evalúan a un valor. Eso no es cierto con referencias de valor sin nombre, pero pueden mostrar un comportamiento polimórfico. Entonces para resolverlo,
las referencias de valor sin nombre se convierten en valores x . Pueden ser calificados y potencialmente tener su tipo dinámico diferente. Ellos, como se pretendía, prefieren referencias de valor durante la sobrecarga, y no se unen a referencias de valor no constantes.
Lo que antes era un valor r (literales, objetos creados por conversiones a tipos que no son de referencia) ahora se convierte en un prvalue . Tienen la misma preferencia que los valores x durante la sobrecarga.
Lo que antes era un lvalue sigue siendo un lvalue.
Y se realizan dos agrupaciones para capturar aquellas que pueden calificarse y pueden tener diferentes tipos dinámicos ( valores ) y aquellas en las que la sobrecarga prefiere el enlace de referencia de valores ( valores ).
fuente
He luchado con esto durante mucho tiempo, hasta que encontré la explicación de cppreference.com de las categorías de valor .
En realidad es bastante simple, pero creo que a menudo se explica de una manera que es difícil de memorizar. Aquí se explica muy esquemáticamente. Citaré algunas partes de la página:
fuente
Un valor C ++ 03 sigue siendo un valor C ++ 11, mientras que un valor C ++ 03 se denomina prvalue en C ++ 11.
fuente
Una adición a las excelentes respuestas anteriores, en un punto que me confundió incluso después de haber leído Stroustrup y pensé que entendía la distinción rvalue / lvalue. Cuando veas
int&& a = 3
,Es muy tentador leer el
int&&
tipo y concluir quea
es un valor. No es:a
tiene un nombre y es ipso facto un valor. No pienses en el&&
como parte del tipo dea
; es solo algo que te dice quéa
está permitido unirse.Esto es particularmente importante para los
T&&
argumentos de tipo en constructores. Si tú escribesFoo::Foo(T&& _t) : t{_t} {}
va a copiar
_t
ent
. NecesitasFoo::Foo(T&& _t) : t{std::move(_t)} {}
si quieres moverte ¡Ojalá mi compilador me advirtiera cuando dejé elmove
!fuente
a
se le permite enlazar": Claro, pero en la línea 2 y 3 sus variables son c y b, y no es a lo que se une, y el tipo dea
es irrelevante aquí, ¿no? Las líneas serían las mismas sia
se declararaint a
. La principal diferencia real aquí es que en la línea 1 a no tiene que serconst
unir a 3.Como las respuestas anteriores cubrieron exhaustivamente la teoría detrás de las categorías de valores, hay otra cosa que me gustaría agregar: en realidad puedes jugar con ella y probarla.
Para algunos experimentos prácticos con las categorías de valores, puede utilizar el especificador decltype . Su comportamiento distingue explícitamente entre las tres categorías de valores primarios (xvalue, lvalue y prvalue).
Usar el preprocesador nos ahorra algo de tipeo ...
Categorías primarias:
Categorías mixtas:
Ahora podemos reproducir (casi) todos los ejemplos de cppreference en la categoría de valor .
Estos son algunos ejemplos con C ++ 17 (para terse static_assert):
Las categorías mixtas son un poco aburridas una vez que descubres la categoría principal.
Para obtener más ejemplos (y experimentación), consulte el siguiente enlace en el explorador del compilador . Sin embargo, no te molestes en leer la asamblea. Agregué muchos compiladores solo para asegurarme de que funciona en todos los compiladores comunes.
fuente
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
que en realidad debería verse#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
lo que sucede si ustedes&&
dosIS_GLVALUE
.