Lesson 3: The Lifecycle of an Object (Constructors & Destructors)
Lesson Overview
This lesson explores how objects are initialized, copied, assigned, and safely deallocated from memory in C++. You will learn how to automate setup and teardown tasks, manage dynamic heap memory, and prevent memory leaks and double-free crashes using the Rule of Three.
π 1. The βWhyβ & Concept Breakdown
Every object in C++ undergoes a strict lifecycle: it is created (born), it performs actions (lives), and it is destroyed (dies).
graph TD
A["[Object Birth]<br>Implicitly invokes CONSTRUCTOR"] --> B["[Object Life]<br>Member functions execute; state modified"]
B --> C["[Object Death]<br>Implicitly invokes DESTRUCTOR<br>(Frees heap memory/resources)"]
style A fill:#2d3748,stroke:#48bb78,stroke-width:2px,color:#fff
style B fill:#2d3748,stroke:#4299e1,stroke-width:2px,color:#fff
style C fill:#2d3748,stroke:#f56565,stroke-width:2px,color:#fff
Key Properties of Constructors
Name Match: Must have the exact same name as the enclosing class.
Automatic Invocation: Called implicitly by the compiler the moment an object is instantiated{allocated in memory at birth}.
No Return Type: Has no return typeβnot even void. Specifying any return type turns it into a normal member function.
Visibility: Normally placed in the public section so that external functions (like main()) can create instances.
Key Properties of Destructors
Name Match with Tilde (~): Same name as the class, preceded by a tilde symbol (~).
Automatic Invocation: Called implicitly when an object goes out of scope {reaches the closing curly brace }} or is explicitly deallocated via delete.
No Parameters & No Overloading: A destructor never takes arguments and cannot be overloaded. Consequently, there is only one destructor per class.
No Return Type: Does not return any value.
The Rule of Three (Memory Safety Principle)
The Rule of Three
If a class manages dynamic heap resources via raw pointers, relying on compiler-generated copy actions causes severe crashes (double-free errors{trying to free already-freed RAM} and memory leaks{orphaned heap RAM that was never released}). If you define any one of these three, you must explicitly define all three:
Destructor: To release allocated heap memory (delete[]).
Copy Constructor: To perform a Deep Copy{allocating a brand new independent memory block} when a new object is initialized from an existing one (ClassName b = a;).
Copy Assignment Operator (operator=): To perform a Deep Copy when an already existing object is assigned to another (b = a;).
β‘ Lifecycles & Memory: Python vs. C++
Constructors: Overloadable setup functions (def __init__ equivalent). Support Member Initializer Lists (: var(val)).
Destructors (~Class): No garbage collector in C++. Stack memory frees automatically at }; heap memory allocated with newmust be freed in destructors (delete[]).
The Assignment Trap:b = a creates pointer aliases in Python. In C++, default b = a shallow-copies pointers, causing double-free crashes. Deep copying requires custom Copy Constructor + operator=.
π 2. Keyword & Syntax Dictionary
Keyword / Symbol
Meaning
Example
ClassName::ClassName()
Default Constructor: Accepts no arguments; sets safe default values.
Book::Book() : price(0.0) {}
ClassName::ClassName(...)
Parameterized Constructor: Accepts arguments to initialize custom state upon creation.
Book::Book(double p) : price(p) {}
ClassName::ClassName(const ClassName &obj)
Copy Constructor: Creates a new object as a deep copy of an existing object. Parameter must be passed by reference.
Book::Book(const Book &b) { price = b.price; }
ClassName& operator=(const ClassName &rhs)
Copy Assignment Operator: Handles deep copying when assigning one existing object to another.
Book& operator=(const Book& rhs);
ClassName::~ClassName()
Destructor: Performs cleanup and frees dynamically allocated memory.
Book::~Book() { delete[] data; }
: (Initializer List)
Constructor Initializer List: Initializes member variables directly before constructor body executes. High performance.
Book(double p) : price(p) {}
new / delete[]
Dynamic Operators:new allocates memory on the Heap; delete[] deallocates heap arrays.
int *arr = new int[5]; delete[] arr;
π» 3. Step-by-Step Code Evolution
Step 3.1: Building a Dynamic Array Class with the Rule of Three
Letβs build an exam-grade dynamic array management class (DynamicArray) implementing all components of the Rule of Three.
#include <iostream>using namespace std;class DynamicArray {private: int* data; int size;public: // 1. Default Constructor DynamicArray() : data(nullptr), size(0) { cout << "[Default Constructor] Empty array created." << endl; } // 2. Parameterized Constructor DynamicArray(int s, int defaultVal = 0) { size = s; data = new int[size]; // Allocating on the heap for (int i = 0; i < size; i++) { data[i] = defaultVal; } cout << "[Parameterized Constructor] Created array of size " << size << "." << endl; } // 3. Copy Constructor (Deep Copy for Initialization) DynamicArray(const DynamicArray& source) { size = source.size; if (source.data != nullptr) { data = new int[size]; // Allocate distinct memory block for (int i = 0; i < size; i++) { data[i] = source.data[i]; // Copy contents independently } } else { data = nullptr; } cout << "[Copy Constructor] Deep Copied array of size " << size << "." << endl; } // 4. Copy Assignment Operator (Deep Copy for Assignment) DynamicArray& operator=(const DynamicArray& rhs) { cout << "[Copy Assignment] Copying state independently." << endl; if (this != &rhs) { // Guard against self-assignment (a = a) delete[] data; // 1. Free existing heap memory (Prevents Memory Leak!) size = rhs.size; if (rhs.data != nullptr) { data = new int[size]; // 2. Allocate fresh independent memory for (int i = 0; i < size; i++) { data[i] = rhs.data[i]; // 3. Copy values } } else { data = nullptr; } } return *this; // Return reference to allow chaining (a = b = c) } // 5. Destructor (Prevents Memory Leaks) ~DynamicArray() { delete[] data; cout << "[Destructor] Deallocated array memory." << endl; } void setValue(int index, int val) { if (index >= 0 && index < size) data[index] = val; } void display() const { if (size == 0) { cout << "Empty." << endl; return; } for (int i = 0; i < size; i++) cout << data[i] << " "; cout << endl; }};
π 4. Memory Visualizations & Execution Order Trace
Trace: Scoped Object Lifecycle (MyString Example)
Understanding constructor and destructor execution order is a staple of lab quizzes:
β οΈ 5. The Examinerβs Trap (Debugging & Quizzes)
Trap 1: Passing by Value to Copy Constructor
Point(Point p) { ... } // β FATAL ERROR
The Cause: Passing by value requires making a copy of the argument, which calls the copy constructor⦠recursively triggering infinite calls until stack overflow.
The Fix: Always pass by reference: Point(const Point& p).
The Cause: Destructors are called automatically by runtime; arguments cannot be supplied.
Trap 3: Constructor/Destructor with Return Types
void Account() { balance = 0.0; } // β ERROR
The Cause: Specifying any return type (even void) turns it into a standard function that is never automatically called on instantiation.
π 6. Practice Quiz
Q1. What is the return type of a destructor?
A) void
B) int
C) Same as class name
D) No return type
Q2. When an object is initialized from another object of the same class at declaration (A b = a;), which function is called?
A) Default Constructor
B) Parameterized Constructor
C) Copy Constructor
D) Copy Assignment Operator
Q3. If a class contains a dynamic heap pointer and you omit a custom copy constructor, what happens upon copying?
A) Compiler generates a deep copy automatically.
B) Compiler generates a shallow copy, risking double-free crashes.
C) Compilation fails.
D) The pointer is set to null.
Q4. Can a destructor be overloaded in C++?
A) Yes, by varying parameter counts.
B) No, a class can have only one destructor.
C) Yes, by changing access specifiers.
D) Only if marked virtual.
Quiz Answers
D β Destructors have no return type.
C β Initialization at declaration invokes the Copy Constructor (operator= is invoked only when assigning to already existing objects).
B β Default compiler action is bit-by-bit shallow copy.
B β Destructors cannot accept arguments, making overloading impossible.
π¬ 7. Viva Quick-Prep
Q1: What is the Rule of Three in C++ memory management?
Answer: It states that if a class requires a custom Destructor, Copy Constructor, or Copy Assignment Operator, it almost certainly requires all three to safely manage dynamic heap resources and avoid memory corruption.
Q2: Why must the copy assignment operator check for self-assignment (if (this != &rhs))?
Answer: In self-assignment (a = a), without this check, the function will deallocate aβs heap memory (delete[] data;) before reading from it, leading to reading corrupted/deleted memory.
Q3: What is the difference between delete and delete[]?
Answer:delete deallocates a single dynamically allocated object, whereas delete[] deallocates an entire dynamically allocated array, ensuring all individual element destructors are properly invoked.