Return Value Optimization
Examples of Copy Elision Under the Hood and Best Practices
Introduction
Return Value Optimization (RVO) is a powerful optimization technique used by C++ compilers to eliminate unnecessary copies and moves of objects when they are returned by value from functions.
This optimization significantly improves performance and reduces overhead, especially for large objects.
What is Return Value Optimization (RVO)?
Return Value Optimization is a compiler optimization that avoids creating temporary objects when returning objects from functions by value.
Instead, the compiler constructs the object directly into the caller’s memory location, eliminating extra constructor calls, copy, and move operations.
Types of RVO
Named Return Value Optimization (NRVO): Occurs when a named object is returned.
Unnamed Return Value Optimization (URVO): Occurs when a temporary unnamed object is returned.
Why Use RVO?
Performance: Avoids costly object copy/move operations.
Efficiency: Reduces resource usage (e.g., memory and CPU).
Cleaner Code: Allows developers to write straightforward return-by-value code without performance penalties.
RVO in Practice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
class Data {
public:
Data(void) {
std::cout << "Default Constructor\n";
}
Data(const Data &) {
std::cout << "Copy Constructor\n";
}
Data(Data &&) {
std::cout << "Move Constructor\n";
}
~Data(void) {
std::cout << "Destructor\n";
}
};
Data createData(void) {
std::cout << "Enter createData\n";
Data d;
std::cout << "Exit createData\n";
return d;
}
int main(void) {
std::cout << "Enter main\n";
Data d = createData();
std::cout << "Leave main\n";
return 0;
}
Output With RVO Disabled
Compile rvo.cpp
with RVO disabled.
1
clang++ -fno-elide-constructors rvo.cpp
Invoke ./a.out
to print the output.
1
2
3
4
5
6
7
8
Enter main
Enter createData
Default Constructor
Exit createData
Move Constructor
Destructor
Leave main
Destructor
Disabling RVO with the -fno-elide-constructors
flag causes the program to first initialize a temporary Data
object (also known as a pure rvalue or prvalue) from Data createData(void)
and then assign it to the Data
object from int main(void)
through move semantics.
The temporary Data
object is then destroyed after the move, followed by the main
Data
object once the program exits.
Output With RVO Enabled (Typical)
Compile rvo.cpp
with default settings.
1
clang++ rvo.cpp
Note the absence of copy/move constructors.
1
2
3
4
5
6
Enter main
Enter createData
Default Constructor
Exit createData
Leave main
Destructor
Here, the compiler elides the copy/move from the returned temporary object by directly constructing the temporary object into main
Data d
’s memory which avoids any temporaries and copy/move operations.
Note that this example is a case of NRVO, and the same optimization applies to URVO as well.
1
2
3
Data createData() {
return Data(); // Compiler applies URVO
}
1
2
3
4
Enter main
Default Constructor
Leave main
Destructor
Again, there are no temporary objects, and no copy or move constructors.
Guaranteed Copy Elision (C++17 and Later)
Starting from C++17, RVO became standardized as guaranteed copy elision for some scenarios, meaning compilers are required to optimize away copies in specific cases.
Guaranteed Copy Elision Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
struct BigData {
BigData() {
std::cout << "Constructed\n";
}
BigData(const BigData &) = delete;
BigData(BigData &&) = delete;
};
BigData createBigData() {
return BigData(); // Guaranteed copy elision
}
int main() {
BigData bd = createBigData();
return 0;
}
Output:
1
Constructed
Even though copy and move constructors are deleted, this still compiles due to guaranteed copy elision.
Best Practices
Trust your compiler: Write code naturally, relying on compiler optimizations.
Prefer returning objects by value for simplicity and clarity, knowing the compiler will optimize via RVO.
Conclusion
Return Value Optimization is a critical optimization in modern C++, making code more efficient without compromising readability or maintainability.
Understanding how and when compilers apply RVO can help you write cleaner and faster C++ code.
Happy Coding!
System Info
- macOS: Apple M4
- Compiler: Clang 20.1.8 (Homebrew LLVM)
- Standard: C++23