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.
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.
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
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 (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++17 Compiler Output (copy elision enabled by default – mandatory)
No Copy/Move Constructor call
C++17 Compiler Output (copy elision disabled flag gets overridden by compiler automatically)
Same as enable elision
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;
}