2.4 Copy Constructor & Destructor
Copy Constructor
A copy constructor creates a new object as a copy of an existing object of the same class. It is invoked automatically in several situations.
Syntax
ClassName(const ClassName& other); // standard form
Example
class Point {
private:
int x, y;
public:
Point(int a, int b) : x(a), y(b) {} // parameterized
Point(const Point& p) : x(p.x), y(p.y) { // copy constructor
cout << "Copy constructor called" << endl;
}
void display() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point p1(5, 10);
Point p2 = p1; // copy constructor called
Point p3(p1); // also calls copy constructor
p1.display(); // (5, 10)
p2.display(); // (5, 10)
p3.display(); // (5, 10)
return 0;
}
---
When is the Copy Constructor Called?
| Scenario | Example |
|---|---|
| Initialisation from another object | Point p2 = p1; |
| Passing object by value to a function | func(p1); |
| Returning object by value from a function | return p1; |
| Initialising an array element with another object | Point arr[] = {p1, p2}; |
| Throwing or catching an object (by value) | throw p1; catch (Point p) |
Note:Point p2 = p1;is initialisation, NOT assignment. The copy constructor is called, notoperator=.
Point p2 = p1; // copy constructor
Point p3;
p3 = p1; // assignment operator (=), NOT copy constructor
---
Default (Compiler-Generated) Copy Constructor
If you don't define a copy constructor, the compiler generates one that does member-wise copy (shallow copy):
class Simple {
public:
int x;
string name;
// no copy ctor defined — compiler generates:
// Simple(const Simple& s) : x(s.x), name(s.name) {}
};
For most classes, the default is sufficient. But for classes managing resources (memory, files, locks), the default is dangerous — it copies pointers/handles, leading to double-free and aliasing bugs.
---
Shallow Copy vs Deep Copy — exam classic
Shallow Copy (default behaviour)
Copies member values directly. For pointer members, this copies the pointer, not the pointed-to data — both objects share the same underlying data.
class Bad {
public:
int* data;
Bad(int v) {
data = new int(v);
}
// Compiler-generated copy ctor: Bad(const Bad& b) : data(b.data) {}
// ↑ shallow: both objects point to SAME memory
~Bad() {
delete data;
}
};
int main() {
Bad a(5);
Bad b = a; // shallow copy — b.data == a.data
return 0;
// 1. b destroyed → deletes data → a.data now dangling
// 2. a destroyed → tries to delete already-freed data → CRASH
}
Deep Copy (custom copy constructor)
Allocates new memory and copies the data itself:
class Good {
public:
int* data;
Good(int v) {
data = new int(v);
}
Good(const Good& other) {
data = new int(*other.data); // ALLOCATE NEW + COPY VALUE
}
~Good() {
delete data;
}
};
int main() {
Good a(5);
Good b = a; // deep copy — b.data is new memory
return 0;
// 1. b destroyed → deletes its own data → fine
// 2. a destroyed → deletes its own data → fine
}
---
Shallow vs Deep Copy — comparison table
| Aspect | Shallow Copy | Deep Copy |
|---|---|---|
| Default behaviour | Yes | No (manual) |
| Pointer copy | Address copied | Pointed-to data copied to new memory |
| Memory | Shared | Separate |
| Risk | Double-free, aliasing bugs | Safe |
| Performance | Fast | Slower (allocates + copies) |
| Use when | Class has no raw pointers | Class manages resources |
---
Why const ClassName& for Copy Constructor?
Point(const Point& other); // standard form
Why const?
To allow copying from a const object: const Point p1(5, 10); Point p2 = p1; (needs const ref).
Why reference & (not value)?
If you passed by value, you'd need to copy the argument — but copying calls the copy constructor — infinite recursion!
Point(Point other); // WRONG — infinite recursion to copy 'other'
---
The Rule of Three
If your class needs any one of these:
- Custom destructor
- Custom copy constructor
- Custom copy assignment operator
It almost certainly needs all three. They manage the same resource.
class String {
private:
char* data;
size_t len;
public:
// 1. Constructor
String(const char* s) {
len = strlen(s);
data = new char[len + 1];
strcpy(data, s);
}
// 2. Destructor
~String() {
delete[] data;
}
// 3. Copy constructor (deep copy)
String(const String& other) {
len = other.len;
data = new char[len + 1];
strcpy(data, other.data);
}
// 4. Copy assignment
String& operator=(const String& other) {
if (this != &other) { // self-assignment check
delete[] data;
len = other.len;
data = new char[len + 1];
strcpy(data, other.data);
}
return *this;
}
};
Modern: Rule of Five
C++11 added move semantics. If you have any of the three, you may also need:
- Move constructor:
String(String&& other) noexcept - Move assignment:
String& operator=(String&& other) noexcept
Better: Rule of Zero
Design so you don't need any. Use standard library types (std::string, std::vector, std::unique_ptr) which handle copying/moving correctly.
---
Destructor
A destructor is a special member function that is automatically called when an object is destroyed. Used to release resources (memory, files, locks).
Syntax
~ClassName();
Properties
| Property | Meaning |
|---|---|
| Name | ~ followed by class name |
| No return type | Not even void |
| No parameters | Always |
| Cannot be overloaded | Exactly one per class |
| Called automatically | When object goes out of scope, or delete is called |
| Order | Reverse of construction order |
| Should be virtual | If class is intended for inheritance |
| Implicitly inline | If defined inside the class |
Example
class FileHandle {
private:
FILE* fp;
public:
FileHandle(const char* path) {
fp = fopen(path, "r");
cout << "File opened" << endl;
}
~FileHandle() {
if (fp) fclose(fp);
cout << "File closed" << endl;
}
};
int main() {
FileHandle fh("data.txt");
// ... use fh
return 0; // destructor called automatically here
}
---
When is the Destructor Called?
| Scenario | When |
|---|---|
| Local object | At end of enclosing scope (}) |
| Global / static | At program end |
| Heap object | When delete is called |
| Heap array | When delete[] is called |
| Object in container | When container destructs / element is erased |
| Temporary | At end of full expression |
{
Point p; // construction
// ...
} // destruction here
Point* p = new Point;
// ...
delete p; // destruction here
vector<Point> v;
v.push_back(Point()); // temporary destroyed after push_back
// vector destruction → all elements destroyed
---
Order of Construction and Destruction
Construction order
- Base class (Unit III)
- Members in declaration order
- Constructor body
Destruction order
- Destructor body
- Members in reverse declaration order
- Base class destructor
class A { public: A() { cout << "A "; } ~A() { cout << "~A "; } };
class B { public: B() { cout << "B "; } ~B() { cout << "~B "; } };
class C {
A a;
B b;
public:
C() { cout << "C "; }
~C() { cout << "~C "; }
};
int main() {
C obj;
return 0;
}
// Output: A B C ~C ~B ~A
---
Destructor — Use Cases
- Release dynamically-allocated memory
class MyVector {
int* data;
public:
MyVector(int n) { data = new int[n]; }
~MyVector() { delete[] data; }
};
- Close file handles
class Logger {
FILE* log;
public:
Logger() { log = fopen("log.txt", "w"); }
~Logger() { fclose(log); }
};
- Release locks
class LockGuard {
Mutex& m;
public:
LockGuard(Mutex& mx) : m(mx) { m.lock(); }
~LockGuard() { m.unlock(); }
};
- Network / socket cleanup
This pattern — acquire in constructor, release in destructor — is called RAII (Resource Acquisition Is Initialisation) and is the C++ idiom for managing resources.
---
Virtual Destructor — Critical for Inheritance
If you have inheritance + polymorphism, the destructor must be virtual in the base class. Otherwise, delete via base pointer only calls base destructor — derived class's destructor never runs → resource leak.
class Base {
public:
~Base() { cout << "~Base "; } // non-virtual
};
class Derived : public Base {
private:
int* data;
public:
Derived() { data = new int[100]; }
~Derived() { delete[] data; cout << "~Derived "; }
};
Base* b = new Derived();
delete b; // Only ~Base called — ~Derived NEVER RUNS — leak!
// FIX: Make Base destructor virtual
class Base {
public:
virtual ~Base() { cout << "~Base "; }
};
// Now: delete b → ~Derived → ~Base — correct!
Rule: Any class meant to be inherited polymorphically MUST have a virtual destructor.
---
Destructor vs Constructor — exam table
| Aspect | Constructor | Destructor |
|---|---|---|
| Name | Same as class | ~ + class name |
| Return type | None | None |
| Parameters | Allowed | None |
| Overloading | Allowed | NOT allowed |
| Default args | Allowed | N/A |
| Auto-called | On construction | On destruction |
| Virtual | No (special case for advanced) | Yes (for inheritance) |
| Purpose | Initialise | Clean up |
| Order | Base → members → body | Body → members reverse → base |
---
Complete Example — Putting It All Together
class String {
private:
char* data;
size_t length;
public:
// Default constructor
String() : data(nullptr), length(0) {
cout << "Default ctor" << endl;
}
// Parameterized constructor
String(const char* s) {
length = strlen(s);
data = new char[length + 1];
strcpy(data, s);
cout << "Param ctor: " << data << endl;
}
// Copy constructor — deep copy
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "Copy ctor: " << data << endl;
}
// Destructor
~String() {
cout << "Dtor: " << (data ? data : "null") << endl;
delete[] data;
}
void display() const {
if (data) cout << data << endl;
}
};
int main() {
String a("Hello");
String b("World");
String c = a; // copy constructor
String d; // default constructor
return 0;
// Destructors called in reverse: d, c, b, a
}
---
Study deep
- Copy constructor + assignment operator are siblings. Copy constructor builds a new object from another. Assignment operator updates an existing object from another. Both must handle deep copy if needed.
= defaultand= deleteare powerful (C++11). You can explicitly request the compiler's version (= default) or disable it (= delete):
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // can't copy
NonCopyable& operator=(const NonCopyable&) = delete;
};
- Self-assignment is real.
a = a;may seem silly but happens in code:p = q;whenp == q. Always checkif (this != &other)in assignment.
- The Rule of Zero is the modern best practice. Use
std::vector,std::string,std::unique_ptras members; the compiler-generated copy/move/destructor will be correct. You only write these when you're implementing a low-level resource type.
- Move semantics (C++11) is the next chapter. Move constructor / move assignment let you transfer ownership without copying — faster than copying for resources. Required for efficient
std::vector/std::stringuse.
Key Terms — Lesson 2.4
The terms below cover copy semantics, destruction, and the Rule of Three/Five/Zero — every PYQ on these expects fluent use.
Copy Constructor — A special constructor with signature ClassName(const ClassName& other) that initialises a new object as a copy of an existing one. Invoked when an object is passed by value, returned by value, or initialised from another object (String b = a;). The compiler synthesises one (member-wise copy) if you don't write it.
Implicit Copy — When the compiler automatically generates a copy constructor that does a member-wise copy of each data member. Sufficient for classes with only value-type members; inadequate for classes that manage raw pointers or other resources.
Shallow Copy — A copy operation that copies pointer values, not the data they point to. After a shallow copy, two objects share the same underlying memory; modifying one affects the other; destroying both leads to double-free. The default compiler-generated copy is a shallow copy.
Deep Copy — A copy operation that allocates new memory and duplicates the data the original pointer pointed to. The two objects then own independent resources. Required whenever a class manages a raw pointer or other resource that should not be shared.
Destructor — A special member function with the name ~ClassName() that is automatically called when an object is destroyed — at end of scope (stack), via delete (heap), end of program (global). Its job is to release resources the object owns (memory, file handles, locks).
Destructor Properties — (1) Same name as class with ~ prefix. (2) No return type. (3) No parameters, so cannot be overloaded — exactly one destructor per class. (4) Automatically called at object destruction. (5) Destruction order is reverse of construction. (6) Can be virtual (and must be for polymorphic base classes). (7) Cannot be inherited.
Order of Destruction — Objects are destroyed in the reverse order of construction. Stack objects in a function: last declared, first destroyed. Members of a class: reverse of their declaration order, regardless of initialiser-list order. Base classes: reverse of inheritance order.
Constructor-Destructor Symmetry — The principle that whatever a constructor acquires, the corresponding destructor releases. RAII operationalises this — destructor cleans up exactly what the constructor set up.
Self-Assignment — The case a = a, which can happen indirectly via aliased pointers: p = q when p == q. A correct copy-assignment operator handles self-assignment, typically by checking if (this != &other) return *this; before any destructive operation.
Rule of Three (C++98) — "If a class needs a user-defined destructor, copy constructor, or copy assignment operator, it almost certainly needs all three." These three functions manage the same resource; defining only one usually means the others are wrong. The classical guideline for resource-owning classes.
Rule of Five (C++11) — Adds move constructor and move assignment operator to the Rule of Three. With move semantics in C++11, resource-owning classes need to handle moves too — making it five special members.
Rule of Zero (Modern) — "Use existing resource-managing types (std::vector, std::string, std::unique_ptr) as members; the compiler-generated copy/move/destructor will then be correct." The aim is to avoid writing any of the five by relying on standard-library RAII types. The dominant idiom in modern C++.
Move Constructor (C++11) — A constructor that transfers ownership of resources from a temporary or expiring object to a new object, rather than copying. Signature: ClassName(ClassName&& other) using rvalue reference &&. Much faster than copying for large resources.
Move Assignment Operator (C++11) — The assignment counterpart of move constructor: ClassName& operator=(ClassName&& other). Transfers resources from other to *this, leaving other in a valid but unspecified state. Both used heavily inside std::vector and the STL.
Move Semantics — The C++11 mechanism (std::move, rvalue references, move constructors / assignment) that lets resources be transferred from one object to another instead of copied. Reduces unnecessary copies, especially for return values and std::vector operations.
std::move — A standard-library function that casts its argument to an rvalue reference, signalling "this object's contents can be moved from." Despite the name, std::move does not move anything — it just enables move semantics in the next constructor / assignment call.
= default (C++11) — Explicitly requests the compiler-generated version of a special member function: Class() = default;. Useful when you want the default behaviour but a class feature would otherwise suppress synthesis.
= delete (C++11) — Disables a special member function: Class(const Class&) = delete; makes the class non-copyable. Useful for resources that logically cannot be copied — file handles, threads, mutexes.
Non-Copyable Class — A class designed to prevent copying — copy constructor and copy assignment are both = delete (or private with no implementation in pre-C++11). Common for resource handles. std::unique_ptr, std::thread, std::mutex are non-copyable in the standard library.
Smart Pointer (Resource Ownership Hint) — Smart pointers like std::unique_ptr and std::shared_ptr model resource ownership in the type system. Using them as members of your class eliminates the need to write a destructor, copy constructor, or copy assignment — the standard library handles cleanup correctly.
Object Slicing — When a derived object is copied (by value) into a base-class variable, the derived part is sliced off — only the base subobject is copied. Base b = derived; slices. Avoid by passing/storing polymorphic objects by reference or pointer, not by value.
Temporary Object — A short-lived object created by the compiler during expression evaluation (return values, intermediate results). Modern C++ uses return value optimisation (RVO) and move semantics to avoid copying temporaries when possible.
RVO / NRVO (Return Value Optimisation) — Compiler optimisations that elide the copy/move when a function returns an object by value. The returned object is constructed directly in the caller's destination, with no copy or move. RVO is named (NRVO) when the returned object is a named local variable.
Virtual Destructor — A destructor declared virtual (virtual ~Base() {}), required when the class is used as a polymorphic base — i.e., when objects of a derived class are destroyed via a base-class pointer. Without a virtual destructor in that scenario, only the base destructor runs and derived resources leak. Covered fully in Unit III.
---
PYQ pattern (very common): "What is a copy constructor? When is it called? Differentiate shallow and deep copy with example." — Define copy constructor; list 5 scenarios when called; table shallow vs deep copy (5 differences); show String example with deep copy.
PYQ pattern: "What is a destructor? List its properties." — Define; 7 properties (name, no return, no params, no overload, auto-called, reverse order, virtual for inheritance); show example.
PYQ pattern: "Differentiate constructor and destructor." — Table 7-8 differences (name, return, params, overloading, defaults, virtual, purpose, order).
PYQ pattern: "Write a C++ program demonstrating constructors and destructors." — Class with default, parameterized, copy ctors + destructor; create objects in different ways; show order of calls.