What Is the Copy Constructor?

The copy constructor is a special constructor that creates a new object as a copy of an existing object of the same type. C++ calls the copy constructor automatically whenever an object is copied, such as when passing by value or initializing one object from another.

When you copy an object, how does C++ know how to copy it? The answer: the copy constructor.

What is a copy constructor?

A copy constructor is a special constructor that creates a new object as a copy of an existing object:

class Point
{
public:
    int x{};
    int y{};

    Point(int xPos, int yPos)
        : x{ xPos }, y{ yPos }
    {
    }

    // Copy constructor
    Point(const Point& source)
        : x{ source.x }, y{ source.y }
    {
    }
};

The copy constructor takes a const reference to an object of the same type and copies its members.

When is the copy constructor called?

Passing by value:

void display(Point p)  // p is a copy
{
    std::cout << "(" << p.x << ", " << p.y << ")\n";
}

int main()
{
    Point original{5, 10};
    display(original);  // Copy constructor called

    return 0;
}

Returning by value:

Point createPoint()
{
    Point temp{3, 7};
    return temp;  // Copy constructor called (maybe)
}

(Modern compilers often optimize this away through Return Value Optimization.)

Direct initialization:

Point p1{4, 8};
Point p2{ p1 };     // Copy constructor called
Point p3 = p1;      // Also calls copy constructor (not assignment!)

Compiler-generated copy constructor

If you don't define a copy constructor, the compiler generates one automatically. It performs a memberwise copy—copying each member individually:

class Data
{
public:
    int value{};
    double amount{};
};

int main()
{
    Data original{ 42, 3.14 };
    Data copy{ original };  // Compiler-generated copy constructor
    // copy.value == 42
    // copy.amount == 3.14

    return 0;
}

Why define your own copy constructor?

For classes managing resources (dynamic memory, file handles, etc.), you need a custom copy constructor:

class IntArray
{
private:
    int* data{};
    int size{};

public:
    IntArray(int length)
        : size{ length }
    {
        data = new int[length]{};
    }

    // Copy constructor
    IntArray(const IntArray& source)
        : size{ source.size }
    {
        data = new int[size]{};  // Allocate new memory
        for (int i{}; i < size; ++i)
            data[i] = source.data[i];  // Copy elements
    }

    ~IntArray()
    {
        delete[] data;
    }
};

Without the custom copy constructor, both objects would share the same data pointer (shallow copy). When one is destroyed, the other has a dangling pointer.

Shallow vs. deep copy

Shallow copy (default behavior):

class Container
{
public:
    int* ptr{};
};

// Compiler-generated copy just copies the pointer
Container c1{};
Container c2{ c1 };  // c1.ptr and c2.ptr point to same memory!

Deep copy (custom copy constructor):

class Container
{
public:
    int* ptr{};

    Container(const Container& source)
    {
        ptr = new int{ *source.ptr };  // Allocate new memory, copy value
    }
};

Copy constructor signature

The copy constructor must take a const reference:

// Correct
ClassName(const ClassName& source);

// Wrong: would require copying to pass argument (infinite recursion!)
ClassName(ClassName source);

Preventing copies

Sometimes you don't want objects copied. Delete the copy constructor:

class Unique
{
public:
    int id{};

    Unique(int i) : id{ i } {}

    Unique(const Unique&) = delete;  // Prevent copying
};

int main()
{
    Unique obj1{42};
    Unique obj2{ obj1 };  // Compile error!

    return 0;
}

Copy constructor with output

#include <iostream>

class Demo
{
public:
    int value{};

    Demo(int v) : value{ v }
    {
        std::cout << "Constructor: " << value << '\n';
    }

    Demo(const Demo& source) : value{ source.value }
    {
        std::cout << "Copy constructor: " << value << '\n';
    }
};

Demo makeDemo()
{
    return Demo{10};
}

int main()
{
    std::cout << "Creating obj1\n";
    Demo obj1{5};

    std::cout << "Creating obj2 from obj1\n";
    Demo obj2{ obj1 };

    std::cout << "Calling makeDemo\n";
    Demo obj3{ makeDemo() };

    return 0;
}

This shows when copies occur (though compilers may optimize some away).

Best Practice
Use const reference parameter (ClassName(const ClassName& source)). Initialize all members to ensure the copy is complete. Deep copy resources for classes managing memory or other resources. Consider the Rule of Three - if you define a copy constructor, you likely need a copy assignment operator and destructor too. Delete the copy constructor with = delete for uncopyable types. Prefer pass-by-const-reference to avoid unnecessary copies.
// Creates unnecessary copies
void process(Data d);

// Better: no copy needed
void process(const Data& d);

Summary

Copy constructor: A special constructor that creates a new object as a copy of an existing object, taking a const reference to an object of the same type.

When it's called: Copy constructors are invoked when passing objects by value, returning objects by value, and directly initializing one object from another.

Compiler-generated version: If you don't define a copy constructor, the compiler generates one that performs memberwise copying - copying each member individually.

Shallow vs. deep copy: The default memberwise copy creates a shallow copy where pointers are copied directly. For classes managing resources, you need a deep copy constructor that allocates new resources.

Signature requirements: Copy constructors must take a const reference parameter to avoid infinite recursion that would occur if passing by value.

Preventing copies: Delete the copy constructor with = delete to prevent objects from being copied, useful for resource-managing classes where copying doesn't make sense.

The Rule of Three: If you need a custom copy constructor, you likely also need a custom copy assignment operator and destructor to properly manage your class's resources.

The copy constructor is essential for classes that manage resources like dynamic memory, file handles, or network connections. Understanding when it's called and how to implement it correctly prevents resource leaks and enables proper object semantics.