#116 – Crash course on the copy constructor

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.