C++ Copy Elision Cheat Sheet
9/30/2021
Author's Profile Avatar

Copy Elision

Under certain conditions, compilers are permitted to omit the copy and move constructions of class objects even if the omitted copy and move constructors have observable side effects. The omission of the copy and move constructions is called copy elision.
The C++ standard states mandatory copy elisions and non-mandatory copy elisions. Under the conditions of a mandatory copy elision, conforming compilers are forced to omit the stated copy and move constructions. Under the conditions of a non-mandatory copy elision, conforming compilers are free to decide whether or not the copy elision will be performed. Copy elisions not required by mandatory copy elision and non-mandatory copy elision are considered implementation-specific extensions and are not part of the standard C++.
Implementation-specific extensions will not be not listed in this cheat sheet. Behaviors prior to C++11 will also not be listed in this cheat sheet.

General Principles

Until C++17
  • No mandatory copy elisions. (GP1)
  • When initializing an object from a prvalue of the same type, the copy and move construction of the object under initialization can be omitted. (GP2, non-mandatory copy elision)
Since C++17
  • Only one mandatory copy elision stated. (GP3)
  • When initializing an object from a prvalue of the same type, the copy and move construction of the object under initialization must be omitted. (GP4, mandatory copy elision)
All Standard Versions
  • In certain contexts and under certain conditions (which will be listed in later sections), when initializing an object from a non-temporary object, the copy and move construction of the object under initialization can be omitted. (GP5, non-mandatory copy elision)

Context-Dependent Behaviors

Function Return Values

Function Returning prvalue (RVO)

Example:
class Foo;
Foo createFoo();

Foo foo() {
  return createFoo();  // !
}
Behaviors:
Until C++17
As long as the following condition holds:
  • The operand of the return statement is a prvalue of the same type as the function's return type.
GP2: non-mandatory copy elision, the copy and move construction of the return value object that happens at // ! can be omitted.
Since C++17
As long as the following condition holds:
  • The operand of the return statement is a prvalue of the same type as the function's return type.
GP4: mandatory copy elision, the copy and move construction of the return value object that happens at // ! must be omitted.

Function Returning Local Object (NRVO)

Example:
class Foo;

Foo foo() {
  Foo a;
  return a;  // !
}
Behaviors:
All Standard Versions
As long as the following condition holds:
  • The operand of the return statement is a non-volatile object with automatic storage duration of the same type as the function's return type ignoring cv-qualification.
GP5: non-mandatory copy elision, the copy and move construction of the return value object that happens at // ! can be omitted.

Function Returning Other Expressions

Example:
class Foo;

Foo foo() {
  Foo a;
  return std::move(a);  // !
}
Behaviors:
All Standard Versions
No copy elision rules apply. The copy and move construction of the return value object that happens at // ! cannot be omitted.

Initialization of Class Objects

Initialize Class Object from prvalue

Example:
class Foo;
Foo createFoo();

void foo() {
  Foo b(createFoo());  // !
}
Behaviors:
Until C++17
As long as the following condition holds:
  • The initializer is a prvalue of the same type as the object under initialization.
GP2: non-mandatory copy elision, the copy and move construction that happens at // ! can be omitted.
Since C++17
As long as the following condition holds:
  • The initializer is a prvalue of the same type as the object under initialization.
GP4: mandatory copy elision, the copy and move construction that happens at // ! must be omitted.

Initialize Class Object from Other Expressions

Example:
class Foo;

void foo() {
  Foo a;
  Foo b(a);  // !
}
Behaviors:
All Standard Versions
No copy elision rules apply. The copy and move construction that happens at // ! cannot be omitted.

Exception Handling

Initialize Exception Object from a throw statement

Example:
class Foo;

void foo() {
  Foo a;
  try {
    throw a;  // !
  } catch (...) {}
}
Behaviors:
All Standard Versions
As long as the following conditions hold:
  • The operand of throw is a non-volatile object with automatic storage duration;
  • The operand of throw is not a function parameter or an object caught by a catch block;
  • The scope of the operand of throw extends past the inner-most try block (if any).
GP5: non-mandatory copy elision, the copy and move construction that happens at // ! can be omitted.

Initialize Exception Object caught by a catch block

Example:
class Foo;

void foo() {
  try { /*do something*/ }
  catch (Foo a) {}  // !
}
Behaviors:
All Standard Versions
As long as the following conditions hold:
  • The type of the exception object caught is the same as Foo ignoring cv-qualification;
  • The omission of the copy construction that happens at // ! does not change the program's observable behavior other than:
    • skipping the copy construction;
    • skipping the destruction of the caught exception object a;
GP5: non-mandatory copy elision, the copy construction that happens at // ! can be omitted.