There are places in the standard where an object is copied or moved in order to initialize an object. Copy elision (sometimes called return value optimization) is an optimization whereby, under certain specific circumstances, a compiler is permitted to avoid the copy or move even though the standard says that it must happen.
Consider the following function:
std::string get_string()
{
return std::string("I am a string.");
}
According to the strict wording of the standard, this function will initialize a temporary std::string
, then copy/move that into the return value object, then destroy the temporary. The standard is very clear that this is how the code is interpreted.
Copy elision is a rule that permits a C++ compiler to ignore the creation of the temporary and its subsequent copy/destruction. That is, the compiler can take the initializing expression for the temporary and initialize the function's return value from it directly. This obviously saves performance.
However, it does have two visible effects on the user:
The type must have the copy/move constructor that would have been called. Even if the compiler elides the copy/move, the type must still be able to have been copied/moved.
Side-effects of copy/move constructors are not guaranteed in circumstances where elision can happen. Consider the following:
struct my_type
{
my_type() = default;
my_type(const my_type &) {std::cout <<"Copying\n";}
my_type(my_type &&) {std::cout <<"Moving\n";}
};
my_type func()
{
return my_type();
}
What will calling func
do? Well, it will never print "Copying", since the temporary is an rvalue and my_type
is a moveable type. So will it print "Moving"?
Without the copy elision rule, this would be required to always print "Moving". But because the copy elision rule exists, the move constructor may or may not be called; it is implementation-dependent.
And therefore, you cannot depend on the calling of copy/move constructors in contexts where elision is possible.
Because elision is an optimization, your compiler may not support elision in all cases. And regardless of whether the compiler elides a particular case or not, the type must still support the operation being elided. So if a copy construction is elided, the type must still have a copy constructor, even though it will not be called.