Chapter 10

Return Value Optimization

RVO, NRVO, Copy Elision — how the compiler avoids unnecessary copies on return.

In this chapter

  1. Return Value Optimization (RVO)
  2. Named RVO (NRVO)
  3. Copy Elision
  4. When RVO Does Not Happen
1

Return Value Optimization (RVO)

RVO means the compiler is allowed to eliminate temporary objects created for return values — even if those objects have side effects (such as printing in a constructor). This is a deliberate language rule, not just a performance hint.

Without RVO

Returning a temporary normally requires three steps:

Snitch Example() {
    return Snitch();     // ① Constructor (temporary)
}                        // ② Copy/move constructor (into caller)
                         // ③ Destructor (temporary)

int main() {
    Snitch s = Example(); // ④ Copy/move constructor (into s)
                          // ⑤ Destructor (intermediate)
                          // ⑥ Destructor (s, at end of scope)
}

With RVO

The compiler allocates the final object's memory in the caller's stack frame and shares it with the callee. The temporary is never created:

Snitch Example() {
    return Snitch();     // ① Constructor directly into s's memory
}

int main() {
    Snitch s = Example(); // ② Destructor (s, at end of scope)
}                         // No copies, no extra destructor calls
RVO is mandatory in C++17 for prvalue return expressions (unnamed temporaries). The compiler is required to elide the copy, regardless of side effects.

2

Named RVO (NRVO)

NRVO applies when the returned object has a name — i.e. it's a named local variable rather than a temporary constructed in the return statement.

Snitch Example() {
    Snitch s;       // ← has a name
    return s;
}

NRVO is similar to RVO — the compiler can construct s directly in the caller's memory — but it has more restrictions. It is not mandatory (even in C++17), so the compiler may or may not apply it.

If NRVO is not applied, the compiler will fall back to using the move constructor (if available), and then the copy constructor. This is why implementing a move constructor is important for performance-sensitive types.

3

Copy Elision

RVO and NRVO are both subsets of a broader category of optimizations called Copy Elision — the compiler omitting copy or move constructor calls entirely. Copy Elision is not limited to return statements.

Another common case: passing a temporary directly to a function:

void foo(Snitch s) {}

// in main:
foo(Snitch());   // Snitch() is constructed directly into the parameter — no copy
Because copy elision can suppress side effects (e.g. print statements in a copy constructor), you should never write code that depends on copy constructors being called a specific number of times.

4

When RVO Does Not Happen

There are cases where the compiler cannot apply RVO or NRVO:

  1. The compiler can't determine which object to return until runtime (e.g. a conditional return)
  2. Returning an input parameter, a member variable, or a global variable
  3. Returning via std::move() — this explicitly forces a move and prevents RVO
  4. Returning to an assignment operator (as opposed to a copy initialization)
✓ RVO applies
Snitch s = foo();  // init
✗ RVO does NOT apply
Snitch s;
s = foo();  // assignment
Don't write return std::move(localVar); — it prevents NRVO and forces a move instead. Just write return localVar; and let the compiler apply NRVO or fall back to a move automatically.
← Chapter 9 ↑ Index Chapter 11 →