#116 – Crash course on the copy constructor communicated how copy elision removed 2 instances of the copy constructor from executing.
Modern C++ supports 3 ways to construct classes: copy initialization, direct initialization, and list initialization
The apparent copy
Consider this Triangle class:
#include <iostream>
class Triangle {
private:
int m_a {};
int m_b {};
int m_c {};
public:
Triangle(int a, int b, int c)
: m_a { a }
, m_b { b }
, m_c { c }
{
std::cout << "Regular constructor\n";
}
Triangle(const Triangle& other)
: m_a { other.m_a }
, m_b { other.m_b }
, m_c { other.m_c }
{
std::cout << "Copy constructor\n";
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\\n";
}
};
int main(){
Triangle triangle { Triangle { 3, 4, 5 } };
triangle.print();
}
Terminal output:
Regular constructor
Triangle(3, 4, 5)
At first glance, this line looks like it should do two things:
Triangle triangle { Triangle { 3, 4, 5 } };
Conceptually, it looks like:
1. Construct a temporary Triangle from { 3, 4, 5 }. // Normal constructor is invoked
2. Copy that temporary into triangle. // Copy constructor is invoked
3. Destroy the temporary.
If that happened literally, we would expect this output:
Regular constructor
Copy constructor
Triangle(3, 4, 5)
But the actual output does not include:
Copy constructor
The copy was elided. We say the constructor has been elided.
Whilst this improves performance, it is problematic if you’re expecting application code to run in the copy constructor. Therefore, do not contain application code in the copy constructor.
Additionally, as of C++17, copy elision is mandatory.
What the compiler removes
The compiler sees that the temporary object exists only to initialize triangle.
So instead of creating a temporary and copying from it, the compiler constructs triangle directly from the constructor arguments.
This source:
Triangle triangle { Triangle { 3, 4, 5 } };
is effectively treated like:
Triangle triangle { 3, 4, 5 };
Return by value
Copy elision also appears when returning objects by value.
#include <iostream>
class Triangle {
private:
int m_a {};
int m_b {};
int m_c {};
public:
Triangle(int a, int b, int c)
: m_a { a }
, m_b { b }
, m_c { c }
{
std::cout << "Regular constructor\n";
}
Triangle(const Triangle& other)
: m_a { other.m_a }
, m_b { other.m_b }
, m_c { other.m_c }
{
std::cout << "Copy constructor\n";
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\\n";
}
};
Triangle makeTriangle(){
return Triangle { 5, 12, 13 };
}
int main(){
Triangle triangle { makeTriangle() };
triangle.print();
}
// Terminal output:
Regular constructor
Triangle(5, 12, 13)
Again, the source code appears to involve a temporary:
return Triangle { 5, 12, 13 };
Conceptually, older mental models describe this as:
1. Construct a temporary Triangle inside makeTriangle().
2. Copy it into the function's return object.
3. Copy that return object into triangle.
But modern C++ can construct the final object directly.
The compiler elides the intermediate objects and the associated copy construction.
Conclusion
Copy elision is an optimization technique performed by the compiler. It eliminates unnecessary creation of objects. Therefore, do not contain application code in constructors and copy constructors since it might be elided away.
Copy elision lets the compiler skip unnecessary temporary objects. In many modern C++ cases, the final object is constructed directly.