Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

2.4 Copy Constructor & Destructor

Lesson 11 of 22 in the free Object Oriented Programming with C++ notes on Siksha Sarovar, written by Rohit Jangra.

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?

ScenarioExample
Initialisation from another objectPoint p2 = p1;
Passing object by value to a functionfunc(p1);
Returning object by value from a functionreturn p1;
Initialising an array element with another objectPoint 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, not operator=.
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

AspectShallow CopyDeep Copy
Default behaviourYesNo (manual)
Pointer copyAddress copiedPointed-to data copied to new memory
MemorySharedSeparate
RiskDouble-free, aliasing bugsSafe
PerformanceFastSlower (allocates + copies)
Use whenClass has no raw pointersClass 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:

  1. Custom destructor
  2. Custom copy constructor
  3. 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:

  1. Move constructor: String(String&& other) noexcept
  2. 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

PropertyMeaning
Name~ followed by class name
No return typeNot even void
No parametersAlways
Cannot be overloadedExactly one per class
Called automaticallyWhen object goes out of scope, or delete is called
OrderReverse of construction order
Should be virtualIf class is intended for inheritance
Implicitly inlineIf 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?

ScenarioWhen
Local objectAt end of enclosing scope (})
Global / staticAt program end
Heap objectWhen delete is called
Heap arrayWhen delete[] is called
Object in containerWhen container destructs / element is erased
TemporaryAt 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

  1. Base class (Unit III)
  2. Members in declaration order
  3. Constructor body

Destruction order

  1. Destructor body
  2. Members in reverse declaration order
  3. 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

  1. Release dynamically-allocated memory
class MyVector {
    int* data;
public:
    MyVector(int n) { data = new int[n]; }
    ~MyVector() { delete[] data; }
};
  1. Close file handles
class Logger {
    FILE* log;
public:
    Logger() { log = fopen("log.txt", "w"); }
    ~Logger() { fclose(log); }
};
  1. Release locks
class LockGuard {
    Mutex& m;
public:
    LockGuard(Mutex& mx) : m(mx) { m.lock(); }
    ~LockGuard() { m.unlock(); }
};
  1. 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

AspectConstructorDestructor
NameSame as class~ + class name
Return typeNoneNone
ParametersAllowedNone
OverloadingAllowedNOT allowed
Default argsAllowedN/A
Auto-calledOn constructionOn destruction
VirtualNo (special case for advanced)Yes (for inheritance)
PurposeInitialiseClean up
OrderBase → members → bodyBody → 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

  1. 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.
  1. = default and = delete are 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;
};
  1. Self-assignment is real. a = a; may seem silly but happens in code: p = q; when p == q. Always check if (this != &other) in assignment.
  1. The Rule of Zero is the modern best practice. Use std::vector, std::string, std::unique_ptr as members; the compiler-generated copy/move/destructor will be correct. You only write these when you're implementing a low-level resource type.
  1. 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::string use.

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.