11 Classes [class]

11.9 Initialization [class.init]

11.9.6 Copy/move elision [class.copy.elision]

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.
In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object.
If the first parameter of the selected constructor is an rvalue reference to the object's type, the destruction of that object occurs when the target would have been destroyed; otherwise, the destruction occurs at the later of the times when the two objects would have been destroyed without the optimization.105
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile object with automatic storage duration (other than a function parameter or a variable introduced by the exception-declaration of a handler ([except.handle])) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the object directly into the function call's return object
  • in a throw-expression ([expr.throw]), when the operand is the name of a non-volatile object with automatic storage duration (other than a function or catch-clause parameter) that belongs to a scope that does not contain the innermost enclosing compound-statement associated with a try-block (if there is one), the copy/move operation can be omitted by constructing the object directly into the exception object
  • in a coroutine, a copy of a coroutine parameter can be omitted and references to that copy replaced with references to the corresponding parameter if the meaning of the program will be unchanged except for the execution of a constructor and destructor for the parameter copy object
  • when the exception-declaration of an exception handler ([except.pre]) declares an object of the same type (except for cv-qualification) as the exception object ([except.throw]), the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
    [Note 1: 
    There cannot be a move from the exception object because it is always an lvalue.
    — end note]
Copy elision is not permitted where an expression is evaluated in a context requiring a constant expression ([expr.const]) and in constant initialization ([basic.start.static]).
[Note 2: 
It is possible that copy elision is performed if the same expression is evaluated in another context.
— end note]
[Example 1: class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f(); struct A { void *p; constexpr A(): p(this) {} }; constexpr A g() { A loc; return loc; } constexpr A a; // well-formed, a.p points to a constexpr A b = g(); // error: b.p would be dangling ([expr.const]) void h() { A c = g(); // well-formed, c.p can point to c or be dangling }
Here the criteria for elision can eliminate the copying of the object t with automatic storage duration into the result object for the function call f(), which is the non-local object t2.
Effectively, the construction of t can be viewed as directly initializing t2, and that object's destruction will occur at program exit.
Adding a move constructor to Thing has the same effect, but it is the move construction from the object with automatic storage duration to t2 that is elided.
— end example]
[Example 2: class Thing { public: Thing(); ~Thing(); Thing(Thing&&); private: Thing(const Thing&); }; Thing f(bool b) { Thing t; if (b) throw t; // OK, Thing(Thing&&) used (or elided) to throw t return t; // OK, Thing(Thing&&) used (or elided) to return t } Thing t2 = f(false); // OK, no extra copy/move performed, t2 constructed by call to f struct Weird { Weird(); Weird(Weird&); }; Weird g(bool b) { static Weird w1; Weird w2; if (b) return w1; // OK, uses Weird(Weird&) else return w2; // error: w2 in this context is an xvalue } int& h(bool b, int i) { static int s; if (b) return s; // OK else return i; // error: i is an xvalue } decltype(auto) h2(Thing t) { return t; // OK, t is an xvalue and h2's return type is Thing } decltype(auto) h3(Thing t) { return (t); // OK, (t) is an xvalue and h3's return type is Thing&& } — end example]
[Example 3: template<class T> void g(const T&); template<class T> void f() { T x; try { T y; try { g(x); } catch (...) { if (/*...*/) throw x; // does not move throw y; // moves } g(y); } catch(...) { g(x); g(y); // error: y is not in scope } } — end example]
105)105)
Because only one object is destroyed instead of two, and one copy/move constructor is not executed, there is still one object destroyed for each one constructed.