Post

Return Value Optimization

Examples of Copy Elision Under the Hood and Best Practices

Return Value Optimization

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
This post is licensed under CC BY 4.0 by the author.