En C++ lvalue (valor de la izquierda) es una expresión que se refiere a un objeto en memoria que tiene un nombre y puede ser referenciado (por ejemplo, una variable). rvalue (valor de la derecha) es un valor temporal que no tiene nombre y no es un objeto en el sentido tradicional (por ejemplo, el resultado de a + b, literales como 5).
Se puede tomar la dirección de un lvalue, pero no de un rvalue (hasta la llegada de las referencias a rvalue). Para pasar estos a funciones existen:
void foo(const std::string& s); — aceptan lvalue y rvalue.void bar(std::string& s); — aceptan solo lvalue.void baz(std::string&& s); — aceptan solo rvalue.Ejemplo:
void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue
Las referencias rvalue son necesarias para la transmisión eficiente de objetos temporales, principalmente para la semántica de movimiento, para mover recursos en lugar de copiarlos.
¿Qué tipo de referencia (lvalue o rvalue) obtendrá la expresión
std::move(obj)? ¿De qué categoría se convertirá el mismo objeto después de aplicar std::move?
Respuesta:
std::move(obj) siempre devuelve una referencia rvalue (T&&), pero el objeto mismo sigue siendo un lvalue, simplemente se le aplica una conversión explícita. Después de esto, hay que manejar el objeto con mucho cuidado (puede estar en un estado indefinido, pero válido).
Ejemplo:
std::string s = "data"; std::string d = std::move(s); // d recibe los datos de s, s ahora está vacío
Historia
En un gran proyecto, uno de los desarrolladores pasaba objetos temporales a través de una referencia lvalue T& (en lugar de T&& o const T&). Esto resultaba en errores de compilación y copias subóptimas: la semántica de movimiento no se utilizaba, y el rendimiento se reducía en un 40%.
Historia
En el motor frontend, se aplicaba incorrectamente std::move a variables que luego se volvían a utilizar. Esto dejaba a las variables en un estado "destruido", provocando cierres inesperados y fallos en los hilos de renderizado.
Historia
En la biblioteca de serialización, se pasaba un contenedor de tipo std::vector<T> a una función como lvalue, mientras que se esperaba un movimiento. En lugar de mover, se realizaba una costosa copia de una gran cantidad de elementos, lo que afectaba drásticamente el tiempo de serialización en grandes arreglos.