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;orRectangle 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
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.
Broken: shallow copy of an owning raw pointer
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
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).
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.