Introduction
For a technical overview - skip to this answer.
For common cases where copy elision occurs - skip to this answer.
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.
The following example taken from Wikipedia:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
Depending on the compiler & settings, the following outputs are all valid:
Hello World!
A copy was made.
A copy was made.
Hello World!
A copy was made.
Hello World!
This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.
If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.
C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly, and in this case, the copy or move constructor need not be accessible or present:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
Best Answer
The copy constructor exists to make copies. In theory when you write a line like:
The compiler would have to call the copy constructor to copy the return of
foo()
intoc
.Copy elision is a technique to skip calling the copy constructor so as not to pay for the overhead.
For example, the compiler can arrange that
foo()
will directly construct its return value intoc
.Here's another example. Let's say you have a function:
If you call it with an actual argument, the compiler has to invoke the copy constructor so that the original parameter cannot be modified:
But now consider a different example, let's say you call your function like this:
operator+
is going to have to create a temporary object (an rvalue). Instead of invoking the copy constructor before callingdoit()
, the compiler can pass the temporary that was created byoperator+
and pass that todoit()
instead.