Declara una referencia de valor (documento de propuesta de estándares).
Aquí hay una introducción a las referencias de rvalue .
Aquí hay una fantástica mirada en profundidad a las referencias de valor de uno de los desarrolladores de bibliotecas estándar de Microsoft .
PRECAUCIÓN: el artículo vinculado en MSDN ("Referencias de Rvalue: Características de C ++ 0x en VC10, Parte 2") es una introducción muy clara a las referencias de Rvalue, pero hace declaraciones sobre las referencias de Rvalue que alguna vez fueron verdaderas en el borrador de C ++ 11 estándar, pero no es cierto para el final! Específicamente, dice en varios puntos que las referencias de valor pueden unirse a valores, lo que alguna vez fue cierto, pero fue cambiado (por ejemplo, int x; int && rrx = x; ya no se compila en GCC) - drewbarbs 13 de julio de 14 a 16:12
La mayor diferencia entre una referencia C ++ 03 (ahora llamada referencia lvalue en C ++ 11) es que puede unirse a un rvalue como un temporal sin tener que ser constante. Por lo tanto, esta sintaxis ahora es legal:
T&& r = T();
Las referencias de rvalue proporcionan principalmente lo siguiente:
Mueve la semántica . Ahora se puede definir un constructor de movimiento y un operador de asignación de movimiento que tome una referencia de valor r en lugar de la referencia habitual de valor constante. Un movimiento funciona como una copia, excepto que no está obligado a mantener la fuente sin cambios; de hecho, generalmente modifica la fuente de modo que ya no posee los recursos movidos. Esto es ideal para eliminar copias extrañas, especialmente en implementaciones de bibliotecas estándar.
Por ejemplo, un constructor de copia podría verse así:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
Si a este constructor se le pasó un temporal, la copia sería innecesaria porque sabemos que el temporal simplemente se destruirá; ¿Por qué no hacer uso de los recursos que ya se asignaron temporalmente? En C ++ 03, no hay forma de evitar la copia, ya que no podemos determinar que se nos haya pasado temporalmente. En C ++ 11, podemos sobrecargar un constructor de movimiento:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
Observe la gran diferencia aquí: el constructor de movimiento en realidad modifica su argumento. Esto efectivamente "movería" lo temporal al objeto que se está construyendo, eliminando así la copia innecesaria.
El constructor de movimiento se usaría para las referencias temporales y para valores de valor no constantes que se convierten explícitamente en referencias de valor utilizando la std::move
función (solo realiza la conversión). El siguiente código invoca el constructor de movimiento para f1
y f2
:
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
Reenvío perfecto . Las referencias de rvalue nos permiten reenviar adecuadamente los argumentos para las funciones con plantilla. Tome por ejemplo esta función de fábrica:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
Si llamamos factory<foo>(5)
, se deducirá que el argumento es int&
, que no se unirá a un literal 5, incluso si foo
el constructor toma un int
. Bueno, podríamos usarlo A1 const&
, pero ¿y si foo
toma el argumento del constructor por referencia no constante? Para hacer una función de fábrica verdaderamente genérica, tendríamos que sobrecargar la fábrica una A1&
y otra vez A1 const&
. Eso podría estar bien si la fábrica toma 1 tipo de parámetro, pero cada tipo de parámetro adicional multiplicaría la sobrecarga necesaria establecida por 2. Eso es muy rápidamente imposible de mantener.
Las referencias de rvalue solucionan este problema al permitir que la biblioteca estándar defina una std::forward
función que pueda reenviar correctamente las referencias de lvalue / rvalue. Para obtener más información sobre cómo std::forward
funciona, vea esta excelente respuesta .
Esto nos permite definir la función de fábrica de esta manera:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
Ahora, el argumento rvalue / lvalue-ness se conserva cuando se pasa al T
constructor. Eso significa que si factory se llama con un rvalue, T
el constructor se llama con un rvalue. Si la fábrica se llama con un valor l, T
el constructor se llama con un valor l. La función de fábrica mejorada funciona debido a una regla especial:
Cuando el tipo de parámetro de función es de la forma T&&
donde T
es un parámetro de plantilla, y el argumento de función es un valor de tipo l A
, el tipo A&
se utiliza para la deducción de argumento de plantilla.
Por lo tanto, podemos usar la fábrica de esta manera:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
Propiedades de referencia importantes de rvalue :
- Para la resolución de sobrecarga, los valores prefieren vincularse a las referencias de valor y los valores prefieren vincularse a las referencias de valor . Por eso los temporales prefieren invocar un constructor de movimiento / operador de asignación de movimiento sobre un constructor de copia / operador de asignación.
- Las referencias de rvalue se unirán implícitamente a rvalues ya temporarios que son el resultado de una conversión implícita . es decir
float f = 0f; int&& i = f;
es está bien formado porque float es implícitamente convertible a int; la referencia sería a un temporal que es el resultado de la conversión.
- Las referencias de valor nominadas son valores. Las referencias de valor sin nombre son valores. Esto es importante para entender por qué la
std::move
llamada es necesaria en:foo&& r = foo(); foo f = std::move(r);