RVO, NRVO, Copy Elision — how the compiler avoids unnecessary copies on return.
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.
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)
}
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
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.
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
There are cases where the compiler cannot apply RVO or NRVO:
std::move() — this explicitly forces a move and prevents RVOSnitch s = foo(); // init
Snitch s;
s = foo(); // assignment
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.