copy elision

From cppreference.com
 
 
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements
Jump statements
Functions
function declaration
lambda function declaration
function template
inline specifier
exception specifications (deprecated)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
decltype specifier (C++11)
Specifiers
cv specifiers
storage duration specifiers
constexpr specifier (C++11)
auto specifier (C++11)
alignas specifier (C++11)
Initialization
Literals
Expressions
alternative representations
Utilities
Types
typedef declaration
type alias declaration (C++11)
attributes (C++11)
Casts
implicit conversions
const_cast conversion
static_cast conversion
dynamic_cast conversion
reinterpret_cast conversion
C-style and functional cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
class template
function template
template specialization
parameter packs (C++11)
Miscellaneous
Inline assembly
 

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 cv-unqualified type 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 cv-unqualified type, 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".
  • In a throw-expression, if the operand is the name of a non-volatile object with automatic storage duration, which isn't the function parameter, or a catch clause parameter, and whose scope does not extend past the innermost try-block (if there is a try-block), then copy/move is omitted. When that local variable is constructed, it is constructed directly in the storage where the exception object would otherwise be moved or copied to. (since C++11)
  • When handling an exception, if the argument of the catch clause is of the same type (except for cv-qualification) as the exception object thrown, the copy/move is omitted and the body of the catch clause accesses the exception object directly, as if it was passed by reference. (since C++11)

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, 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, otherwise the program is ill-formed.

[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
}
 
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

[edit] See also