Copying an object
Consider a simple 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 }
{
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\n";
}
};
int main(){
Triangle original { 3, 4, 5 };
Triangle copy { original };
original.print();
copy.print();
}
Terminal output:
Triangle(3, 4, 5)
Triangle(3, 4, 5)
This line constructs copy from another Triangle:
Triangle copy { original };
That calls the copy constructor.
Since we did not define a copy constructor, the compiler generates one for us. The compiler-generated copy constructor performs memberwise copying:
copy.m_a ← original.m_a
copy.m_b ← original.m_b
copy.m_c ← original.m_c
For this class, that is exactly what we want.
Defining your own copy constructor
We can define the copy constructor ourselves:
#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 }
{
}
Triangle(const Triangle& other)
: m_a { other.m_a }
, m_b { other.m_b }
, m_c { other.m_c }
{
std::cout << "Copy constructor called\n";
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\n";
}
};
int main(){
Triangle original { 3, 4, 5 };
Triangle copy { original };
original.print();
copy.print();
}
Terminal output:
Copy constructor called
Triangle(3, 4, 5)
Triangle(3, 4, 5)
The copy constructor receives the source object by const reference:
Triangle(const Triangle& other)
The const matters because copying should not modify the object being copied from.
Access to private members
Inside the copy constructor, this code is legal:
m_a { other.m_a }
m_a is private, and other.m_a is also private.
This is allowed because C++ access control works on a per-class basis, not a per-object basis.
See #111 – C++ private access is class-level, not object-level for a refresher.
Pass by value can call the copy constructor
When a function takes an object by value, the parameter is a separate object.
That parameter may be copy-constructed from the argument:
#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 }
{
}
Triangle(const Triangle& other)
: m_a { other.m_a }
, m_b { other.m_b }
, m_c { other.m_c }
{
std::cout << "Copy constructor called\n";
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\n";
}
};
void printByValue(Triangle triangle){
triangle.print();
}
int main(){
Triangle original { 5, 12, 13 };
printByValue(original);
}
Terminal output:
Copy constructor called
Triangle(5, 12, 13)
The function parameter is declared by value:
void printByValue(Triangle triangle)
So the function receives its own Triangle object.
That object is copy-constructed from original.
If copying is unnecessary, prefer passing by const reference:
void printByReference(const Triangle& triangle){
triangle.print();
}
Return by value and copy elision
Returning an object by value can involve a copy, but modern C++ compilers are smart and eliminate that copy.
This optimization is called copy elision.
#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 called\n";
}
Triangle(const Triangle& other)
: m_a { other.m_a }
, m_b { other.m_b }
, m_c { other.m_c }
{
std::cout << "Copy constructor called\n";
}
void print() const{
std::cout << "Triangle("
<< m_a << ", "
<< m_b << ", "
<< m_c << ")\n";
}
};
Triangle makeTriangle(){
return Triangle { 7, 24, 25 };
}
int main(){
Triangle triangle { makeTriangle() };
triangle.print();
}
Terminal output on a typical modern compiler:
Regular constructor called
Triangle(7, 24, 25)
Even though makeTriangle() returns a Triangle by value, the copy constructor may not be called.
In modern C++, this is often optimized so the returned object is constructed directly where it is needed.
Therefore:
Do not put application code inside a copy constructor since it may be elided away
Using = default
If the compiler-generated copy constructor is exactly what you want, you can explicitly request it:
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 }
{
}
Triangle(const Triangle& other) = default;
};
Using = delete
Sometimes copying should not be allowed.
For example, a type that represents unique ownership of a resource may not make sense to copy.
class TriangleMeshHandle {
public:
TriangleMeshHandle() = default;
TriangleMeshHandle(const TriangleMeshHandle& other) = delete;
};
Now this is not allowed:
TriangleMeshHandle mesh {};
TriangleMeshHandle copy { mesh }; // error: copy constructor is deleted
Takeaway
A copy constructor creates a new object from an existing object of the same type:
ClassName(const ClassName& other)
For simple classes, the compiler-generated copy constructor usually does the right thing by copying each member. Define your own copy constructor only when copying needs special behaviour. Delete it when copying should be forbidden.