copy elision
Optimizes out copy- and move-constructors, resulting in zero-copy pass-by-value semantics.
Contents |
[edit] Explanation
Under the following circumstances, the compilers are permitted to omit the copy- and move-constructors of class objects even if copy/move constructor and the destructor have observable side-effects.
- If a function returns a class type by value, and the return statement's expression is the name of a non-volatile object with automatic storage duration, which isn't the function parameter, or a catch clause parameter, and which has the same type (ignoring top-level cv-qualification) as the return type of the function, then copy/move is omitted. When that local variable is constructed, it is constructed directly in the storage where the function's return value would otherwise be moved or copied to. This variant of copy elision is known as NRVO, "named return value optimization".
- When a nameless temporary, not bound to any references, would be moved or copied into an object of the same type (ignoring top-level cv-qualification), the copy/move is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be moved or copied to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".
|
(since C++11) |
When copy elision occurs, the implementation treats the source and target of the omitted copy/move 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 rvalue reference to object type, the destruction occurs when the target would have been destroyed) (since C++17)
Multiple copy elisions may be chained to eliminate multiple copies.
[edit] Notes
Copy elision is the only allowed form of optimization 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.
Even when copy elision takes place and the copy-/move-constructor is not called, it must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
If copy elision cannot be performed, the return statement will attempt to use the move constructor to initialize the by-value return object (even if the argument of the return statement is lvalue), and only if that is unavailable, a copy would be performed. |
(since C++11) |
[edit] Example
#include <vector> #include <iostream> struct Noisy { Noisy() {std::cout << "constructed\n"; } Noisy(const Noisy&) { std::cout << "copied\n"; } Noisy(Noisy&&) { std::cout << "moved\n"; } ~Noisy() {std::cout << "destructed\n"; } }; std::vector<Noisy> f() { std::vector<Noisy> v = std::vector<Noisy>(3); // copy elision from temporary to v return v; // NRVO from v to the nameless temporary that is returned // if optimizations are disabled, move constructor is called here } void fn_by_val(std::vector<Noisy> arg) { std::cout << "arg.size() = " << arg.size() << '\n'; } int main() { std::vector<Noisy> v = f(); // copy elision from returned temporary to v fn_by_val(f()); // and from temporary to the argument of fn_by_val() }
Possible output:
constructed constructed constructed constructed constructed constructed arg.size() = 3 destructed destructed destructed destructed destructed destructed