Omits copy and move (since C++11) constructors, resulting in zero-copy pass-by-value semantics.
Mandatory elision of copy/move operationsUnder 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
T x = T(T(f())); // only one call to default constructor of T, to initialize x 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. | (since C++17) |
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since 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 (since 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:
| (until C++17) |
Return value optimization is mandatory and no longer considered as copy elision; see above. | (since C++17) |
| (since C++11) |
| (since C++20) |
When copy elision occurs, the implementation treats the source and target of the omitted copy/move (since 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) (since C++11).
Multiple copy elisions may be chained to eliminate multiple copies.
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 | (since C++11) |
Copy elision is the only allowed form of optimization (until C++14)one of the two allowed forms of optimization, alongside allocation elision and extension, (since 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. | (since C++11) |
Feature-test macro | Value | Std | Comment |
---|---|---|---|
__cpp_guaranteed_copy_elision | 201606L | (C++17) | Guaranteed copy elision through simplified value categories |
#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(); // copy elision when initializing v // from a temporary (until C++17) / 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(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { 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) std::cout << "&v = " << &v << '\n'; 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) }
Possible output:
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 1967 | C++11 | when copy elision is done using a move constructor, the lifetime of the moved-from object was still considered | not considered |
CWG 2022 | C++11 | copy elision was optional in constant expressions | copy elision mandatory |
CWG 2278 | C++11 | NRVO was mandatory in constant expressions | forbid NRVO in constant expressions |
CWG 2426 | C++17 | destructor not required when returning a prvalue | destructor is potentially invoked |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/copy_elision