Copy elision
Cuando se cumplen ciertos criterios, la creación de un objeto de clase a partir de un objeto fuente del mismo tipo (ignorando la calificación cv) puede omitirse, incluso si el constructor seleccionado y/o el destructor para el objeto tienen efectos secundarios. Esta omisión de la creación de objetos se denomina copy elision .
Contenidos |
Explicación
La elisión de copia está permitida en las siguientes circunstancias (que pueden combinarse para eliminar múltiples copias):
- En una return statement en una función con un tipo de retorno de clase, cuando el operando es el nombre de un objeto no volátil obj con automatic storage duration (que no sea un parámetro de función o un parámetro de handler ), la copy-initialization del objeto resultado puede omitirse construyendo obj directamente en el objeto resultado de la llamada a función. Esta variante de la eliminación de copia se conoce como named return value optimization (NRVO).
|
(hasta C++17) |
|
(desde C++11) |
|
(desde C++20) |
Cuando ocurre la elisión de copia, la implementación trata la fuente y el destino de la inicialización omitida simplemente como dos formas diferentes de referirse al mismo objeto.
|
La destrucción ocurre en el momento posterior entre los dos momentos en que los dos objetos habrían sido destruidos sin la optimización. |
(until C++11) |
|
Si el primer parámetro del constructor seleccionado es una referencia a valor del tipo del objeto, la destrucción de ese objeto ocurre cuando el objetivo habría sido destruido. De lo contrario, la destrucción ocurre en el momento posterior entre los dos momentos en que los dos objetos habrían sido destruidos sin la optimización. |
(since C++11) |
Semántica de prvalue ("eliminación de copia garantizada")Desde C++17, un prvalue no se materializa hasta que se necesita, y luego se construye directamente en el almacenamiento de su destino final. Esto significa que incluso cuando la sintaxis del lenguaje sugiere visualmente una copia/movimiento (por ejemplo, inicialización por copia ), no se realiza ninguna copia/movimiento, lo que significa que el tipo no necesita tener un constructor de copia/movimiento accesible en absoluto. Los ejemplos incluyen:
T f() { return U(); // constructs a temporary of type U, // then initializes the returned T from the temporary } T g() { return T(); // constructs the returned T directly; no move }
T x = T(T(f())); // x is initialized by the result of f() directly; no move
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // no elision when initializing a base class subobject D(int) : D(g()) {} // no elision because the D object being initialized might // be a base-class subobject of some other class }; Nota: Esta regla no especifica una optimización, y el Estándar no la describe formalmente como "eliminación de copia" (porque no se está eliminando nada). En cambio, la especificación del lenguaje central de C++17 de prvalues y temporales es fundamentalmente diferente de la de revisiones anteriores de C++: ya no hay un temporal del cual copiar/mover. Otra forma de describir la mecánica de C++17 es "paso de valor no materializado" o "materialización temporal diferida": los prvalues se devuelven y se utilizan sin materializar nunca un temporal. |
(desde C++17) |
Notas
La elisión de copia es la única forma permitida de optimización (hasta C++14) una de las dos formas permitidas de optimización, junto con elisión y extensión de asignación , (desde C++14) que puede cambiar los efectos secundarios observables. Debido a que algunos compiladores no realizan la elisión de copia en todas las situaciones donde está permitida (por ejemplo, en modo depuración), los programas que dependen de los efectos secundarios de los constructores de copia/movimiento y destructores no son portables.
|
En una sentencia return o una expresión throw , si el compilador no puede realizar la copy elision pero se cumplen las condiciones para la copy elision, o se cumplirían excepto que la fuente es un parámetro de función, el compilador intentará usar el move constructor incluso si el operando fuente está designado por un lvalue (until C++23) el operando fuente será tratado como un rvalue (since C++23) ; consulte return statement para más detalles. En constant expression y constant initialization , la copy elision nunca se realiza. struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(since C++11) |
| Macro de prueba de características | Valor | Std | Característica |
|---|---|---|---|
__cpp_guaranteed_copy_elision
|
201606L
|
(C++17) | Eliminación de copia garantizada mediante categorías de valor simplificadas |
Ejemplo
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (hasta C++17) elisión de copia inicializando v desde un temporal; // puede llamarse al constructor de movimiento // (desde C++17) "elisión de copia garantizada" return v; // elisión de copia ("NRVO") desde v al objeto resultado; // puede llamarse al constructor de movimiento } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (hasta C++17) elisión de copia inicializando v desde el resultado de f() // (desde C++17) "elisión de copia garantizada" std::cout << "&v = " << &v << '\n'; g(f()); // (hasta C++17) elisión de copia inicializando arg desde el resultado de f() // (desde C++17) "elisión de copia garantizada" }
Salida posible:
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1967 | C++11 |
cuando se realiza la elisión de copia usando un constructor de movimiento,
aún se consideraba el tiempo de vida del objeto del que se movió |
no se considera |
| CWG 2426 | C++17 | no se requería el destructor al retornar un prvalue | el destructor se invoca potencialmente |
| CWG 2930 | C++98 |
solo las operaciones de copia/movimiento podían ser elididas, pero un
constructor no-copia/movimiento puede ser seleccionado por inicialización-copia |
elide cualquier construcción de objeto
de inicializaciones-copia relacionadas |