What is Copy Elision, RVO & NRVO?

Share the Article

What is copy elision ?

Copy elision is an optimization technique in which the compiler ignores the class’s copy and move constructors. In such case the compiler code itself creates a copy of temporary object. This is a decent compiler technique and it applies it when given source object is a temporary object.

Before, C++17, the elision was optional .This means, by default, it was the individual compiler’ decision. regarding when to apply this optimization.

Examples of copy elision

1. Creation of a new object with a temporary object

The RHS of following code creates a temporary object of class MainFunda. This temporary shall initialize a new object, called obj. The temporary object then destruct just after this statement.

MainFunda obj = MainFunda();

Therefore, when a compiler applies elision technique, then no copy or move constructors will invoke.

2. During Return Value Optimization (RVO)

RVO is a special kind of copy elision. The compiler may apply RVO when a function returns a temporary object.

In following example, the return value of function myRVO is temporary. In regular case, the compiler may call a copy constructor to initialize an object from this temporary. However, in this case, the compiler shall not invoke any copy or move constructor.

MainFunda myRVO() { return MainFunda( ); //return temporary } //Calling code MainFunda obj = myRVO(); //temporary initializes obj
3. Initializing a function parameter using a temporary object

When a function specifies a class object as parameters. And the parameter is of type by-value, then in normal case, a copy constructor initializes such parameter objects. However, when the calling code passes a temporary object then compiler may apply copy elision.

int funda(MainFunda obj) { } //Calling code int x = funda( MainFunda() ); //passing temporary object

Copy elision is mandatory in C++17

Since C++17, the copy elision rule is made mandatory. Therefore, in all above scenarios, the compiler shall by default apply this optimization. However, the user can avoid this by using following compiler flag

g++ -std=c++17 -fno-elide-constructors ce.cpp

Proof that copy elision has become mandatory in C++17

In the following example, the class MainFunda has removed both copy constructor and move constructor. However, the first line of main( ) function needs a copy operation.

#include <iostream> //main header using namespace std; //for namespace class MainFunda { public: MainFunda(const MainFunda&) = delete; MainFunda(MainFunda&&) = delete; MainFunda() { cout <<"MainFunda()"<< endl; } ~MainFunda() { cout <<"~MainFunda()"<< endl; } }; int main() { MainFunda a1 = MainFunda(); //Needs copy operation return 0; }

Output of this program in C++14

In C++ 14, the compilation fails.

This is because, the compiler is looking for copy operations inside class but do not finds they are deleted.

C++14 compiler error when copy and move operations are disabled in class

Output of this program in C++17

In C++17, the compilation is successful.

This is because, the compiler directly applies Copy Elision technique. It do not tries to find if there is any copy or move operations available.

C++17 compiler successfully compiles code due to copy elision. Even when copy and move operations are disabled.

Example of copy elision when derived class do not have copy and move

In following example, the base class has valid move and copy constructors. However, the derived class has deleted both these constructors.

Now, the example, shall prove that:

  • The copy done using copy elision in derived finally calls base-class copy constructor
#include <iostream> //main header using namespace std; //for namespace class MFBase { public: MFBase() { cout << "MFBase()" <<endl; } MFBase(const MFBase& rhs) { cout << "MFBase(const MFBase&)" << endl; } }; class MFDerived : public MFBase { public: MFDerived(const MFDerived&) = delete; MFDerived(MFDerived&&) = delete; MFDerived() = default; }; int main() { MFDerived a1 = MFDerived(); //construct from temporary return 0; }

Output in C++17

In C++17, optimization shall work in inheritance

Prove that Copy Elision is optional in C++14

In following example, the C++14 compiler, though, applies copy elision by default. However, when the user provides a flag to disable it, then compiler do not apply. Therefore, we shall see that in such case, the compiler shall invoke constructor to initialize the new object.

Whereas, the C++17, shall always apply the elision, even when the user provides compiler flag. Therefore, this will never call the user-defined copy or move constructor.

#include <iostream> //main header using namespace std; //for namespace class MainFunda { public: MainFunda() { cout << "MainFunda()" <<endl; } MainFunda(const MainFunda& rhs) { cout << "MainFunda(const MainFunda&)" << endl; } MainFunda(MainFunda&& rhs) { cout << "MainFunda(MainFunda&&)" << endl; } }; int main() { MainFunda a1 = MainFunda(); return 0; }
C++14 Compiler Output (copy elision enabled by default)

No Copy/Move Constructor call

C++14 compiler output when optimization is enabled.
C++14 Compiler Output (copy elision disabled with flag)

Gets Move Constructor call. Because, first compiler calls constructor for temporary object and then it calls move to assign the resource to a1.

C++14 compiler output when optimization is disabled.
C++17 Compiler Output (copy elision enabled by default – mandatory)

No Copy/Move Constructor call

C++17 compiler output with optimization is enabled mandatorily.
C++17 Compiler Output (copy elision disabled flag gets overridden by compiler automatically)

Same as enable elision

C++17 compiler output when trying to disable copy elision optimization with compiler flags.

What is the difference between Return Value Optimization (RVO) and Named Return Value Optimization (NRVO)?

As explained RVO comes into effect when a function returns a temporary object. And, the NRVO applies when function returns a named object.

RVO Example
MainFunda myRVO() { return MainFunda( ); //return temporary } //Calling code MainFunda obj = myRVO(); //temporary initializes obj
NRVO Example
MainFunda myNRVO() { MainFunda obj1; return obj1; //return named object } //Calling code MainFunda obj2 = myNRVO(); //return value initializes obj2

NRVO is not mandatory in C++17

RVO is different concept than NRVO. Although RVO is mandatory but this is not same for NRVO. Therefore, the user may use compiler flag to turn this off. In such case, the compiler shall use Move operation defined in class to construct the object from function’s return value.

In the following example, the compiler shall apply NRVO by default. However, once, optimization is turned off, the compiler shall start using move calls during return of function myNRVO.

This RVO behavior shall be different, in C++17, RVO will be mandatory and compiler flag cannot disable it.

#include <iostream> //main header using namespace std; //for namespace class MainFunda { public: MainFunda() { cout << "MainFunda()" <<endl; } MainFunda(const MainFunda& rhs) { cout << "MainFunda(const MainFunda&)" << endl; } MainFunda(MainFunda&& rhs) { cout << "MainFunda(MainFunda&&)" << endl; } }; MainFunda myNRVO() { MainFunda obj1; return obj1; //return named object } int main() { //Calling code MainFunda obj2 = myNRVO(); //return value initializes obj2 return 0; }

C++17 Output (NRVO shall be enabled by default in this compiler)
C++17 compilation with NRVO enabled.
C++17 Output (NRVO disabled by compiler flag)
C++17 compilation with NRVO disabled.

Main Funda: In C++17, Copy elision including Return Value Optimization (RVO) is mandatory. But Named Return Value Optimization (NRVO) is not mandatory.

Related Topics:

Lambda in C++11
Lambda in C++17
What are the drawbacks of using enum ?
Which member functions are generated by compiler in class?
C++ Multithreading: Understanding Threads
How to stop compiler from generating special member functions?
Compiler Generated Destructor is always non-virtual
How to make a class object un-copyable?
Why virtual functions should not be called in constructor & destructor ?
Explaining C++ casts
How std::forward( ) works?
Rule of Three
How std::move() function works?
What is reference collapsing?
How delete keyword can be used to filter polymorphism
emplace_back vs push_back

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *