Se permitió que la elisión de copias ocurriera en varias circunstancias. Sin embargo, incluso si estuviera permitido, el código aún tenía que poder funcionar como si la copia no se hubiera eliminado. Es decir, tenía que haber un constructor de copia y / o movimiento accesible.
Garantizada copia elisión redefine una serie de conceptos C ++, de tal manera que ciertas circunstancias en las que se podrían elididas copias / se mueve en realidad no provocan una copia / mover en absoluto . El compilador no está eliminando una copia; el estándar dice que tal copia nunca podría ocurrir.
Considere esta función:
T Func() {return T();}
Bajo las reglas de elisión de copia no garantizadas, esto creará un valor temporal, luego pasará de ese temporal al valor de retorno de la función. Esa operación de movimiento se puede elidir, pero T
aún debe tener un constructor de movimiento accesible incluso si nunca se usa.
Similar:
T t = Func();
Esta es la inicialización de copia de t
. Esto copiará initialize t
con el valor de retorno de Func
. Sin embargo, T
todavía debe tener un constructor de movimientos, aunque no se llamará.
La elisión de copia garantizada redefine el significado de una expresión de valor . Pre-C ++ 17, prvalues son objetos temporales. En C ++ 17, una expresión prvalue es simplemente algo que puede materializar un temporal, pero todavía no es temporal.
Si usa un prvalue para inicializar un objeto del tipo prvalue, no se materializa ningún temporal. Cuando lo hace return T();
, esto inicializa el valor de retorno de la función a través de un prvalue. Dado que esa función regresa T
, no se crea ningún temporal; la inicialización del prvalue simplemente inicia directamente el valor de retorno.
Lo que hay que entender es que, dado que el valor de retorno es un prvalue, todavía no es un objeto . Es simplemente un inicializador de un objeto, tal como T()
es.
Cuando lo hace T t = Func();
, el prvalue del valor de retorno inicializa directamente el objeto t
; no hay una etapa de "crear un temporal y copiar / mover". Dado que Func()
el valor devuelto es un prvalue equivalente a T()
, t
se inicializa directamente por T()
, exactamente como si lo hubiera hecho T t = T()
.
Si un prvalue se usa de cualquier otra manera, el prvalue materializará un objeto temporal, que será usado en esa expresión (o descartado si no hay expresión). Entonces, si lo hiciera const T &rt = Func();
, el valor prvalue materializaría un temporal (usando T()
como inicializador), cuya referencia se almacenaría rt
, junto con el material de extensión de vida temporal habitual.
Una cosa que la elisión garantizada le permite hacer es devolver objetos que están inmóviles. Por ejemplo, lock_guard
no se puede copiar ni mover, por lo que no puede tener una función que lo devuelva por valor. Pero con la elisión de copia garantizada, puede hacerlo.
La elisión garantizada también funciona con inicialización directa:
new T(FactoryFunction());
Si se FactoryFunction
devuelve T
por valor, esta expresión no copiará el valor devuelto en la memoria asignada. En su lugar, asignará memoria y utilizará la memoria asignada como memoria de valor de retorno para la llamada de función directamente.
Entonces, las funciones de fábrica que regresan por valor pueden inicializar directamente la memoria asignada al montón sin siquiera saberlo. Siempre que estos funcionen internamente sigan las reglas de elisión de copia garantizada, por supuesto. Tienen que devolver un valor de tipo T
.
Por supuesto, esto también funciona:
new auto(FactoryFunction());
En caso de que no le guste escribir nombres de tipos.
Es importante reconocer que las garantías anteriores solo funcionan para prvalues. Es decir, no obtiene ninguna garantía al devolver una variable con nombre :
T Func()
{
T t = ...;
...
return t;
}
En este caso, t
aún debe tener un constructor de copia / movimiento accesible. Sí, el compilador puede optar por optimizar la copia / movimiento. Pero el compilador aún debe verificar la existencia de un constructor de copia / movimiento accesible.
Así que nada cambia para la optimización del valor de retorno con nombre (NRVO).