# Elisión de copia

< cpp‎ | language

Omite los constructores de copia y movimiento (desde C++11), que resulta en semántica de cero copia, paso por valor.

## Contenido

### [editar]Explicación

#### Elisión de operaciones de copia/movimiento obligatoria

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:

T f() {
return T();
}

f(); // only one call to default constructor of T
The destructor of the type returned must be accessible at the point of the return statement and non-deleted, even though no T object is destroyed.
• In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
This can only apply when the object being initialized is known not to be a potentially-overlapping subobject:
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
};

Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.

(desde C++17)

#### [editar]Elisión de operaciones de copia/movimiento (desde C++11) no obligatoria

Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (desde C++11) construction of class objects even if the copy/move (desde C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (desde C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

• In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
 In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object. When the nameless temporary is the operand of a return statement, this variant of copy elision is known as RVO, "return value optimization". (hasta C++17) Return value optimization is mandatory and no longer considered as copy elision; see above. (desde C++17)
 In a throw-expression, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and whose scope does not extend past the innermost try-block (if there is a try-block). In a catch clause, when the argument is of the same type (ignoring cv-qualification) as the exception object thrown, the copy of the exception object is omitted and the body of the catch clause accesses the exception object directly, as if caught by reference (there cannot be a move from the exception object because it is always an lvalue). This is disabled if such copy elision would change the observable behavior of the program for any reason other than skipping the copy constructor and the destructor of the catch clause argument (for example, if the catch clause argument is modified, and the exception object is rethrown with throw). (desde C++11) In coroutines, copy/move of the parameter into coroutine state may be elided where this does not change the behavior of the program other than by omitting the calls to the parameter's constructor and destructor. This can take place if the parameter is never referenced after a suspension point or when the entire coroutine state was never heap-allocated in the first place. (desde C++20)

When copy elision occurs, the implementation treats the source and target of the omitted copy/move (desde C++11) operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization (except that, if the parameter of the selected constructor is an rvalue reference to object type, the destruction occurs when the target would have been destroyed) (desde C++17).

Multiple copy elisions may be chained to eliminate multiple copies.

 In constant expression and constant initialization, return value optimization (RVO) is guaranteed, however, named return value optimization (NRVO) is forbidden: struct A { void *p; constexpr A(): p(this) {} };   constexpr A g() { A a; return a; }   constexpr A a; // a.p points to a // constexpr A b = g(); // error: b.p would be dangling and would point to a temporary // with automatic storage duration   void h() { A c = g(); // c.p may point to c or to an ephemeral temporary }   extern const A d; constexpr A f() { A e; if (&e == &d) return A(); else return e; // mandating NRVO in constant evaluation contexts would result in contradiction // that NRVO is performed if and only if it's not performed } // constexpr A d = f(); // error: d.p would be dangling (desde C++14)

### [editar]Notas

Copy elision is the only allowed form of optimization (hasta C++14)one of the two allowed forms of optimization, alongside allocation elision and extension, (desde C++14) that can change the observable side-effects. Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.

 In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details. (desde C++11)

### [editar]Ejemplo

#include <iostream>
#include <vector>

struct Noisy {
Noisy() { std::cout << "constructed\n"; }
Noisy(const Noisy&) { std::cout << "copy-constructed\n"; }
Noisy(Noisy&&) { std::cout << "move-constructed\n"; }
~Noisy() { std::cout << "destructed\n"; }
};

std::vector<Noisy> f() {
std::vector<Noisy> v = std::vector<Noisy>(3); // copy elision when initializing v
// from a temporary (until C++17)
// from a prvalue (since C++17)
return v; // NRVO from v to the result object (not guaranteed, even in C++17)
}             // if optimization is disabled, the move constructor is called

void g(std::vector<Noisy> arg) {
std::cout << "arg.size() = " << arg.size() << '\n';
}

int main() {
std::vector<Noisy> v = f(); // copy elision in initialization of v
// from the temporary returned by f() (until C++17)
// from the prvalue f() (since C++17)
g(f());                     // copy elision in initialization of the parameter of g()
// from the temporary returned by f() (until C++17)
// from the prvalue f() (since C++17)
}

Posible salida:

constructed
constructed
constructed
constructed
constructed
constructed
arg.size() = 3
destructed
destructed
destructed
destructed
destructed
destructed

### [editar]Reportes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
CWG 2022 C++14 La elisión de copia era opcional en espresiones constantes. La elisión de copia es obligatoria.
CWG 2278 C++14 NRVO era obligatoria en expresiones constantes. Se prohibe NRVO en expresiones constantes
CWG 2426 C++17 No se requiere el destructor cuando se devuelve un prvalue. Se invoca potencialmente al destructor.