Quién es quién en C++: lvalue, rvalue, xvalue y otros «valores»

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Qué se esconde detrás de las letras l, r, x, gl y pr? Analicemos las categorías de valores en C++

\n\n

Si alguna vez has escrito código en C++ y has recibido un error como "cannot bind non-const lvalue reference to an rvalue", entonces ya te has encontrado con esta «magia». Las categorías de valores — lvalue, rvalue, xvalue, glvalue y prvalue — no son solo una clasificación académica. Son la base sobre la que se sustenta la semántica de movimiento (move semantics) y el reenvío perfecto (perfect forwarding).

\n\n

Aclaremos de una vez por todas qué significa cada letra, cómo distinguirlas en la práctica y por qué sin este conocimiento es imposible escribir C++ moderno y eficiente.

\n\n

Breve historia: ¿de dónde surgieron estos cinco términos?

\n\n

Originalmente, en la época de C, solo existían dos conceptos: lvalue (valor izquierdo — lo que puede estar a la izquierda de una asignación) y rvalue (valor derecho — lo que está a la derecha). Todo era simple: una variable es lvalue, un número es rvalue.

\n\n

Con la llegada de C++11 y la semántica de movimiento, este panorama simple dejó de funcionar. Aparecieron las referencias a rvalue (&&) y la necesidad de distinguir objetos temporales «moribundos». Así nació la nueva clasificación, que el estándar de C++ describe a través de cinco categorías.

\n\n

Observa la jerarquía (diagrama en forma de texto):

\n\n
        expression
        /        \
   glvalue      rvalue
   /      \    /      \
lvalue   xvalue   prvalue
\n\n

Recuerda esta imagen — es la clave de todo.

\n\n

lvalue (left value) — objetos «longevos»

\n\n

lvalue es una expresión que hace referencia a un objeto que tiene una dirección en memoria. Dicho objeto no desaparecerá después de que termine la expresión. Puedes tomar su dirección, modificarlo, y seguirá existiendo.

\n\n

Ejemplos de lvalue:

\n
    \n
  • Nombre de variable: int x = 5; x es un lvalue.
  • \n
  • Puntero desreferenciado: *ptr.
  • \n
  • Referencia a una variable: int& ref = x; ref.
  • \n
  • Literal de cadena: "hello" (sí, es un lvalue, aunque constante).
  • \n
\n\n

Código:

\n
int main() {\n    int a = 10;\n    int* p = &a;   // Tomar dirección — ok, a es lvalue\n    int& ref = a;  // Referencia a lvalue\n\n    // &(a + 1);   // ¡Error! a+1 es rvalue, no tiene dirección\n    return 0;\n}
\n\n

rvalue (right value) — valores «desechables»

\n\n

rvalue es una expresión que no tiene una dirección permanente en memoria. Solo existe en el momento de evaluar la expresión. No puedes tomar su dirección (con raras excepciones).

\n\n

Los rvalue incluyen:

\n
    \n
  • Literales (excepto cadenas): 42, 3.14f, true.
  • \n
  • Resultados de operaciones aritméticas: a + b.
  • \n
  • Objetos temporales: std::string("temp").
  • \n
  • Expresiones lambda.
  • \n
\n\n

Código:

\n
int foo() { return 5; }\n\nint main() {\n    int x = 42;       // 42 — rvalue\n    int y = x + 10;   // x+10 — rvalue\n    // int* p = &(x + 10); // ¡Error! No se puede tomar la dirección de un rvalue\n    return 0;\n}
\n\n

prvalue (pure rvalue) — rvalue «puros»

\n\n

prvalue es el rvalue clásico de C++98. Es un valor temporal que no tiene nombre y no está vinculado a la memoria. A este prvalue solemos llamarlo simplemente rvalue en el lenguaje coloquial.

\n\n

Ejemplos de prvalue:

\n
    \n
  • Números, enumeraciones, resultados de operadores.
  • \n
  • Llamada a una función que devuelve una no-referencia: std::to_string(123).
  • \n
  • Objeto temporal creado explícitamente: MyClass().
  • \n
\n\n

xvalue (expiring value) — objetos «moribundos»

\n\n

Aquí es donde empieza lo más interesante. xvalue es un objeto que pronto será destruido, pero cuyos recursos aún pueden «salvarse» (moverse). Este es un pu

Blogs

Book Recommendations