He estado buscando algunas de las nuevas características de C ++ 11 y una que he notado es el doble ampersand al declarar variables, como T&& var
.
Para empezar, ¿cómo se llama esta bestia? Desearía que Google nos permitiera buscar puntuaciones como esta.
¿Qué significa exactamente ?
A primera vista, parece ser una referencia doble (como los punteros dobles de estilo C T** var
), pero me cuesta pensar en un caso de uso para eso.
c++
c++11
rvalue-reference
c++-faq
perfect-forwarding
paxdiablo
fuente
fuente
:)
Respuestas:
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 .
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:
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í:
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:
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 paraf1
yf2
: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:
Si llamamos
factory<foo>(5)
, se deducirá que el argumento esint&
, que no se unirá a un literal 5, incluso sifoo
el constructor toma unint
. Bueno, podríamos usarloA1 const&
, pero ¿y sifoo
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 unaA1&
y otra vezA1 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ómostd::forward
funciona, vea esta excelente respuesta .Esto nos permite definir la función de fábrica de esta manera:
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:Por lo tanto, podemos usar la fábrica de esta manera:
Propiedades de referencia importantes de rvalue :
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.std::move
llamada es necesaria en:foo&& r = foo(); foo f = std::move(r);
fuente
Named rvalue references are lvalues. Unnamed rvalue references are rvalues.
; sin saber esto, he luchado por entender por qué la gente haceT &&t; std::move(t);
mucho tiempo en los movimientos, y cosas por el estilo.int x; int &&rrx = x;
ya no se compila en CCG)typename identity<T>::type& a
equivalente aT&
?Denota una referencia de valor. Las referencias de Rvalue solo se unirán a objetos temporales, a menos que se genere explícitamente lo contrario. Se utilizan para hacer que los objetos sean mucho más eficientes bajo ciertas circunstancias, y para proporcionar una instalación conocida como reenvío perfecto, que simplifica enormemente el código de la plantilla.
En C ++ 03, no puede distinguir entre una copia de un valor l no mutable y un valor r.
En C ++ 0x, este no es el caso.
Considere la implementación detrás de estos constructores. En el primer caso, la cadena debe realizar una copia para retener la semántica de valores, lo que implica una nueva asignación de montón. Sin embargo, en el segundo caso, sabemos de antemano que el objeto que se pasó a nuestro constructor debe destruirse de inmediato, y no tiene que permanecer intacto. Efectivamente, podemos intercambiar los punteros internos y no realizar ninguna copia en este escenario, que es sustancialmente más eficiente. La semántica de movimiento beneficia a cualquier clase que tenga una copia costosa o prohibida de recursos referenciados internamente. Considere el caso de
std::unique_ptr
: ahora que nuestra clase puede distinguir entre temporales y no temporales, podemos hacer que la semántica de movimiento funcione correctamente para queunique_ptr
no se pueda copiar pero se pueda mover, lo que significa questd::unique_ptr
puede almacenarse legalmente en contenedores estándar, ordenados, etc., mientras que C ++ 03std::auto_ptr
no.Ahora consideramos el otro uso de referencias rvalue: reenvío perfecto. Considere la cuestión de vincular una referencia a una referencia.
No puedo recordar lo que C ++ 03 dice sobre esto, pero en C ++ 0x, el tipo resultante cuando se trata de referencias de valor es crítico. Una referencia de valor r a un tipo T, donde T es un tipo de referencia, se convierte en una referencia del tipo T.
Considere la función de plantilla más simple: min y max. En C ++ 03 debe sobrecargar manualmente las cuatro combinaciones de const y no const. En C ++ 0x es solo una sobrecarga. Combinado con plantillas variadas, esto permite un reenvío perfecto.
Dejé la deducción del tipo de devolución, porque no puedo recordar cómo se hizo de forma espontánea, pero ese mínimo puede aceptar cualquier combinación de valores, valores, valores constantes.
fuente
std::forward<A>(aref) < std::forward<B>(bref)
? y no creo que esta definición sea correcta cuando intentes avanzarint&
yfloat&
. Mejor suelte una plantilla de formulario de tipo.El término para
T&&
cuando se usa con deducción de tipo (como para el reenvío perfecto) se conoce coloquialmente como referencia de reenvío . El término "referencia universal" fue acuñado por Scott Meyers en este artículo , pero luego fue cambiado.Esto se debe a que puede ser el valor r o el valor l.
Ejemplos son:
Se puede encontrar más discusión en la respuesta para: Sintaxis para referencias universales
fuente
Una referencia rvalue es un tipo que se comporta de manera muy similar a la referencia ordinaria X &, con varias excepciones. La más importante es que cuando se trata de la resolución de sobrecarga de funciones, los valores prefieren las referencias de valor antiguas, mientras que los valores prefieren las nuevas referencias de valor:
Entonces, ¿qué es un valor? Cualquier cosa que no sea un valor. Un valor de l es una expresión que se refiere a una ubicación de memoria y nos permite tomar la dirección de esa ubicación de memoria a través del operador &.
Es casi más fácil entender primero qué valores logran con un ejemplo:
Los operadores de construcción y asignación se han sobrecargado con versiones que toman referencias de valor. Las referencias de Rvalue permiten que una función se bifurque en tiempo de compilación (a través de la resolución de sobrecarga) con la condición "¿Me están llamando en un lvalue o un rvalue?". Esto nos permitió crear operadores de asignación y constructores más eficientes que mueven recursos en lugar de copiarlos.
El compilador se bifurca automáticamente en el momento de la compilación (dependiendo de si se invoca para un valor l o un valor r) eligiendo si se debe llamar al constructor de movimiento o al operador de asignación de movimiento.
Resumiendo: las referencias de valor permiten la semántica de movimiento (y el reenvío perfecto, discutido en el siguiente enlace del artículo).
Un ejemplo práctico y fácil de entender es la plantilla de clase std :: unique_ptr . Dado que unique_ptr mantiene la propiedad exclusiva de su puntero sin procesar subyacente, unique_ptr no se puede copiar. Eso violaría su invariante de propiedad exclusiva. Por lo tanto, no tienen constructores de copia. Pero tienen constructores de movimiento:
static_cast<unique_ptr<int[]>&&>(ptr)
generalmente se hace usando std :: moveUn excelente artículo que explica todo esto y más (como cómo los valores permiten un reenvío perfecto y lo que eso significa) con muchos buenos ejemplos es la explicación de las referencias de valor de C ++ de Thomas Becker . Esta publicación se basó en gran medida en su artículo.
Una introducción más corta es Una breve introducción a las referencias de Rvalue por Stroutrup, et. Alabama
fuente
Sample(const Sample& s)
también necesita copiar el contenido? La misma pregunta para el 'operador de asignación de copia'.