CppCopy Constructor

Copy Constructor

A copy constructor creates a new object as a copy of an existing object of the same class. C++ calls it automatically in several common situations, often without you writing new or explicitly asking for a copy at all.

What triggers a copy
  • Passing an object by value into a function.

  • Returning an object by value from a function (in older compilers without guaranteed copy elision — modern compilers often optimize this away, but the copy constructor must still exist and be accessible).

  • Explicitly copy-constructing: Rectangle b = a; or Rectangle b(a);.

  • Inserting an object by value into a container, e.g. std::vector<Rectangle>::push_back(a).

The compiler-generated copy constructor

If you don't declare your own, the compiler generates a copy constructor that performs a shallow copy — it copies each member variable, one by one, exactly as it is stored. For plain data (numbers, std::string, other well-behaved class types) this is exactly what you want.

Shallow copy works fine for plain data

CPP
class Rectangle {
public:
    double width;
    double height;
};

int main() {
    Rectangle a{3.0, 4.0};
    Rectangle b = a; // compiler-generated copy constructor: member-by-member copy

    b.width = 99.0;  // 'a' is completely unaffected — safe, independent copies
}
Where shallow copy breaks down

The default member-by-member copy becomes dangerous the moment a class holds a raw pointer to a resource it owns (heap memory, a file handle, and so on). Copying the pointer copies the address, not the resource — both objects end up pointing at the same thing.

Double free from a shallow copy
In the example below, `a` and `b` both hold a pointer to the exact same heap allocation. When both objects are destroyed, their destructors each call `delete` on the same address — the second `delete` is undefined behavior (a “double free”), and real programs typically crash or corrupt memory here.

Broken: shallow copy of an owning raw pointer

CPP
class Buffer {
public:
    int* data;

    Buffer(int size) : data(new int[size]) {}
    ~Buffer() { delete[] data; } // frees whatever 'data' points to
};

int main() {
    Buffer a(10);
    Buffer b = a; // shallow copy: b.data == a.data (same address!)

    // When main() ends, ~Buffer() runs for 'b' then for 'a'.
    // Both destructors call delete[] on the SAME pointer -> undefined behavior.
}
Writing a deep-copy constructor

The fix is to write your own copy constructor that allocates a new, independent resource and copies the underlying data into it, instead of copying the pointer itself.

Fixed: a deep-copy constructor

CPP
class Buffer {
public:
    int* data;
    int size;

    Buffer(int n) : data(new int[n]), size(n) {}

    // Deep-copy constructor: allocate our own memory, then copy the values.
    Buffer(const Buffer& other) : data(new int[other.size]), size(other.size) {
        for (int i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }

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

int main() {
    Buffer a(10);
    Buffer b = a; // deep copy: b.data is a distinct allocation from a.data

    // Each destructor now frees its own memory. No double free.
}
The Rule of Three

The Buffer example demonstrates the classic Rule of Three: if a class needs a custom destructor, it almost certainly needs a custom copy constructor and a custom copy assignment operator too — all three exist to manage the same resource correctly, and writing only one or two usually leaves a hole (for example, assignment via operator= still shallow-copies unless you override it as well).

Note
In modern C++, the best practice is often to avoid raw owning pointers altogether and use a smart pointer or a standard container (`std::vector`, `std::unique_ptr`) instead — those already implement correct copy/move/destroy semantics, so your class doesn't need to write any of this by hand. This is sometimes called the “Rule of Zero.”
What's next
  • Copying a large resource is often wasteful when the source object is about to be discarded anyway — the Move Constructor page covers how to transfer ownership instead of duplicating data.

  • Together, destructor + copy constructor + copy assignment + move constructor + move assignment make up the Rule of Five.