Difference between revisions of "cpp/language/copy elision"
m (note 'unmaterialized value passing' (an idea kicked around on reflector)) |
(treat copy elision related to initialization) |
||
Line 1: | Line 1: | ||
{{title|Copy elision}} | {{title|Copy elision}} | ||
− | {{cpp/language/navbar}} | + | {{cpp/language/initialization/navbar}} |
Omits copy-{{rev inl|since=c++11| and move-}}constructors, resulting in zero-copy pass-by-value semantics. | Omits copy-{{rev inl|since=c++11| and move-}}constructors, resulting in zero-copy pass-by-value semantics. | ||
===Explanation=== | ===Explanation=== | ||
− | {{ | + | {{rrev|since=c++17| |
− | + | ||
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. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually: | 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. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually: | ||
Line 22: | Line 21: | ||
Note: the rule above does not specify an optimization: C++17 core language specification of {{rlp|value category|prvalues}} and {{rlp|implicit_conversion#Temporary_materialization|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. | Note: the rule above does not specify an optimization: C++17 core language specification of {{rlp|value category|prvalues}} and {{rlp|implicit_conversion#Temporary_materialization|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. | ||
}} | }} | ||
− | |||
Under the following circumstances, the compilers are permitted, but not required to omit the copy-{{rev inl|since=c++11| and move-}}construction of class objects even if the copy{{rev inl|since=c++11|/move}} constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed. | Under the following circumstances, the compilers are permitted, but not required to omit the copy-{{rev inl|since=c++11| and move-}}construction of class objects even if the copy{{rev inl|since=c++11|/move}} constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed. | ||
− | * If a function returns a class type by value, and the {{rlp|return|return statement}}'s expression 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 has the same type (ignoring top-level | + | * If a function returns a class type by value, and the {{rlp|return|return statement}}'s expression 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 has the same type (ignoring top-level {{rlp|cv|cv-qualification}}) as the return type of the function, then copy{{rev inl|since=c++11|/move}} is omitted. When that local object 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". |
{{rev begin}} | {{rev begin}} | ||
{{rev|until=c++17| | {{rev|until=c++17| | ||
− | * When a nameless temporary, not bound to any references, would be copied {{rev inl|since=c++11|or moved}} into an object of the same type (ignoring top-level | + | * When a nameless temporary, not bound to any references, would be copied {{rev inl|since=c++11|or moved}} into an object of the same type (ignoring top-level {{rlp|cv|cv-qualification}}), the copy{{rev inl|since=c++11|/move}} is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be copied {{rev inl|since=c++11|or moved}} to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization". |
}} | }} | ||
{{rev|since=c++17| | {{rev|since=c++17| | ||
Line 47: | Line 45: | ||
Multiple copy elisions may be chained to eliminate multiple copies. | Multiple copy elisions may be chained to eliminate multiple copies. | ||
− | {{ | + | {{rrev|since=c++14|{{mark unreviewed dr|CWG|2022}} |
− | + | ||
* In {{rlp|constant expression}} and {{rlp|constant initialization}}, all copy elision is guaranteed (note: this is specified by post-C++14 defect report CWG 2022, which may be reversed pending decision on CWG 2278): | * In {{rlp|constant expression}} and {{rlp|constant initialization}}, all copy elision is guaranteed (note: this is specified by post-C++14 defect report CWG 2022, which may be reversed pending decision on CWG 2278): | ||
{{source|1= | {{source|1= | ||
Line 69: | Line 66: | ||
}} | }} | ||
}} | }} | ||
− | |||
===Notes=== | ===Notes=== | ||
Copy elision is {{rev inl|until=c++14|the only allowed form of optimization}}{{rev inl|since=c++14|one of the two allowed forms of optimization, alongside {{rlp|new#Allocation|allocation elision and extension}},}} 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. | Copy elision is {{rev inl|until=c++14|the only allowed form of optimization}}{{rev inl|since=c++14|one of the two allowed forms of optimization, alongside {{rlp|new#Allocation|allocation elision and extension}},}} 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. | ||
− | {{ | + | {{rrev|since=c++11| |
− | + | 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 {{rlp|return#Notes|return statement}} for details. | |
− | 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 | + | |
}} | }} | ||
− | |||
===Example=== | ===Example=== | ||
Line 141: | Line 135: | ||
===See also=== | ===See also=== | ||
− | * | + | * {{rlp|copy initialization}} |
− | * | + | * {{rlp|copy constructor}} |
− | * | + | * {{rlp|move constructor}} |
− | + | {{langlinks|de|es|fr|it|ja|ko|pt|ru|zh}} | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + |
Revision as of 09:26, 29 June 2018
Omits copy- and move-(since C++11)constructors, resulting in zero-copy pass-by-value semantics.
Contents |
Explanation
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. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
T f() { return T{}; } T x = f(); // only one call to default constructor of T, to initialize x T* p = new T(f()); // only one call to default constructor of T, to initialize *p 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. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
- 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 a 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(since C++11) is omitted. When that local object 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".
|
(until C++17) |
This optimization is mandatory; see above. |
(since C++17) |
|
(since C++11) |
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++17).
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(); // b.p points to b (NRVO guaranteed) void g() { A c = g(); // c.p may point to c or to an ephemeral temporary } |
(since C++14) |
Notes
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) |
Example
#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 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) }
Possible output:
constructed constructed constructed constructed constructed constructed arg.size() = 3 destructed destructed destructed destructed destructed destructed
Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 2022 | C++14 | copy elision was optional in constant expressions | copy elision mandatory |