Keywords: C++ | Copy Elision | Return Value Optimization | Compiler Optimization | C++17
Abstract: This article provides an in-depth exploration of Copy Elision and Return Value Optimization (RVO/NRVO) in C++. Copy elision is a compiler optimization technique that eliminates unnecessary object copying or moving, particularly in function return scenarios. Starting from the standard definition, the article explains how it works, including when it occurs, how it affects program behavior, and the mandatory guarantees in C++17. Code examples illustrate the practical effects of copy elision, and limitations such as multiple return points and conditional initialization are discussed. Finally, the article emphasizes that developers should not rely on side effects in copy/move constructors and offers practical advice.
Introduction
Copy elision is a common compiler optimization technique in C++ aimed at reducing unnecessary object copying or moving operations, thereby improving program performance. It is particularly crucial in scenarios where functions return objects by value, making it feasible to return large objects efficiently. Copy elision is unique in that it can bypass the "as-if" rule, meaning compilers may apply this optimization even if the copy or move operations have side effects.
Basic Concepts of Copy Elision
Copy elision allows compilers to omit the copy or move construction of class objects, even if the associated constructors or destructors have side effects. According to the C++ standard (ISO/IEC 14882:2017, Section 12.8), copy elision is permitted in the following situations:
- In a return statement, when the expression is the name of a non-volatile automatic object with the same type as the function return type, the copy/move can be omitted by constructing the automatic object directly into the return value.
- In a throw-expression, when the operand is the name of a non-volatile automatic object whose scope does not extend beyond the innermost try-block, the copy/move can be omitted by constructing the object directly into the exception object.
- When a temporary class object would be copied/moved to a class object of the same type, the operation can be omitted by constructing the temporary directly into the target.
- In an exception handler declaration, when the declared object has the same type as the exception object, the copy/move can be omitted by treating the declaration as an alias for the exception object.
The core idea of copy elision is to treat the source and target objects as two different references to the same object, avoiding additional construction and destruction calls.
Return Value Optimization (RVO) and Named Return Value Optimization (NRVO)
Return Value Optimization (RVO) is a common form of copy elision that specifically omits copying when a function returns a temporary object. For example:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made."; }
};
C f() {
return C(); // RVO may occur
}Named Return Value Optimization (NRVO) applies when returning a named object:
C g() {
C c;
return c; // NRVO may occur
}Prior to C++17, RVO and NRVO were optional compiler optimizations. However, from C++17 onward, copy elision is guaranteed when an object is returned directly (e.g., return C();), and the copy or move constructor need not be accessible or present. This enhances code portability and performance predictability.
Code Examples and Impact of Copy Elision
Consider the following example demonstrating how copy elision reduces output:
#include <iostream>
struct C {
C() {}
C(const C&) { std::cout << "A copy was made."; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!";
C obj = f();
}Depending on the compiler and settings, outputs may include:
- No copy elision: Outputs "Hello World! A copy was made. A copy was made." (two copies)
- Partial optimization: Outputs "Hello World! A copy was made." (one copy)
- Full optimization: Outputs "Hello World!" (no copies)
This reduces the number of object creations and destructions, so developers should avoid placing critical logic in copy/move constructors or destructors, as their invocation cannot be guaranteed.
Common Scenarios and Limitations of Copy Elision
Copy elision is not limited to return values; it also applies in:
- Constructing objects from temporaries:
Thing t2 = Thing(); - Passing temporaries as parameters:
foo(Thing()); - Throwing and catching exceptions:
throw c;andcatch(Thing c)
However, copy elision has limitations:
- Multiple return points: Optimization may be disabled if a function has multiple return statements pointing to different objects.
- Conditional initialization: Compilers might not apply optimization when objects are initialized within conditional branches.
- Compiler dependency: While most modern compilers support it, the degree of optimization can vary based on settings (e.g., GCC's
-fno-elide-constructorsflag).
Developers can use flags like -fno-elide-constructors (in GCC) to disable copy elision for debugging or testing purposes.
Side Effects and Considerations of Copy Elision
Since copy elision may omit constructor calls with side effects, developers should avoid relying on such side effects. For example:
#include <iostream>
int n = 0;
class ABC {
public:
ABC(int) {}
ABC(const ABC& a) { ++n; } // Side effect: modifies static storage object
};
int main() {
ABC c1(21);
ABC c2 = ABC(21);
std::cout << n; // Outputs 0 if copy is elided; otherwise outputs 1
return 0;
}This code illustrates how copy elision can affect observable behavior, highlighting the need for careful constructor design in performance-critical applications.
Conclusion
Copy elision and return value optimization are essential compiler techniques in C++ that enhance performance by reducing unnecessary object copying. With mandatory guarantees in C++17 for certain scenarios, these optimizations improve code portability. Developers should understand their workings and limitations, avoid depending on side effects in constructors, and leverage optimizations to write efficient, maintainable code. In practice, combining compiler flags and code reviews can maximize the benefits of these optimizations.