⚙️ Unit 10 • C++ Systems Engineering

Constructors & Destructors: Lifecycle & RAII Architecture

Resource acquisition, lifecycle determinism, and destruction sequencing in C++: default, parameterized, and copy constructors, member initialization lists, dynamic constructors, deep vs shallow copy mechanics, destructor execution semantics, and the LIFO stack unwinding guarantee.

🚀 Initialization: Member Initializer Lists
📋 Copy Semantics: Deep Copy vs Pointer Aliasing
🧹 Destruction: Deterministic Stack Unwinding
⏳ Order of Execution: Construction FIFO → Destruction LIFO
01

Constructors: Default, Parameterized & Overloading

A constructor is a specialized member function automatically invoked by the C++ runtime when an object instance is allocated in memory. Its architectural purpose is to initialize object data members and establish class invariants before any client code can access the object.

📐 Inviolable Syntactic Rules for Constructors
  • Must have the exact same name as the enclosing class.
  • Zero return type: Constructors cannot return any value, not even void.
  • Cannot be declared const, virtual, or static.
Default Constructor

Takes zero arguments (or has default values for all arguments). Invoked when declaring Widget w;. If no user constructors are declared, the compiler auto-generates a trivial default constructor.

Parameterized Constructor

Accepts parameters to initialize member fields with explicit caller values upon instantiation (e.g., Widget w(10, "CPU");).

Constructor Overloading

Multiple constructors sharing the class name but distinguished by distinct parameter signatures, offering flexible initialization strategies.

constructors_overview.cpp
#include <iostream>
#include <string>

class DatabaseConnection {
private:
    std::string host;
    int port;
    int timeoutMs;

public:
    // 1. Default Constructor
    DatabaseConnection() 
        : host("localhost"), port(5432), timeoutMs(3000) {
        std::cout << "[Constructor] Default connection established.\n";
    }

    // 2. Parameterized Constructor with Member Initializer List
    DatabaseConnection(std::string h, int p, int timeout)
        : host(std::move(h)), port(p), timeoutMs(timeout) {
        std::cout << "[Constructor] Parameterized connection to " << host << ":" << port << "\n";
    }

    // 3. Overloaded Constructor (Convenience overload)
    DatabaseConnection(std::string h, int p)
        : DatabaseConnection(std::move(h), p, 5000) { // Delegating Constructor (C++11)
        std::cout << "[Constructor] Delegated overload with default timeout.\n";
    }
};

int main() {
    DatabaseConnection c1;                       // Default
    DatabaseConnection c2("db.internal", 6379, 1000); // Parameterized
    DatabaseConnection c3("replica.db", 5433);   // Delegating overload

    return 0;
}
⚠️ The Member Initialization List Order Rule

Class members are initialized in the exact order they are declared in the class definition, NOT in the order they appear in the constructor's initializer list! Reversing the order in the initializer list triggers subtle undefined behavior if one member relies on an uninitialized predecessor.

02

Copy Constructors: Shallow Copy vs Deep Copy Architecture

A Copy Constructor initializes an object using an existing object of the identical class as a prototype. Its signature strictly requires a const reference:

Copy Constructor Signature Axiom

ClassName(const ClassName& other);

Why Pass by Reference? If the parameter were passed by value (ClassName other), passing the argument into the copy constructor would itself require calling the copy constructor, resulting in an infinite recursive loop that exhausts the call stack!

Shallow Copy (Memberwise Copy)

The default compiler-generated copy constructor copies members bit-by-bit. If the class manages raw pointers, it copies only the memory address! Both objects now point to the same heap buffer. When destructors run, both attempt to free the same address, causing a catastrophic Double Free Crash.

Deep Copy (Heap Isolation)

A user-defined copy constructor that allocates an entirely independent new heap memory block for the destination object, copying the underlying data values from the source buffer. Each object maintains total memory autonomy.

Heap Memory Behavior: Shallow vs Deep Copy:
Shallow Copy (Hazardous Pointer Aliasing)
Stack: obj1.ptr (0x5000)
Stack: obj2.ptr (0x5000)
Heap: [0x5000: Data Buffer]
→ 2 Pointers point to 1 Buffer! Destructor triggers Double Free (SIGABRT).
Deep Copy (Safe Memory Duplication)
Stack: obj1.ptr (0x5000) → Heap: [0x5000]
Stack: obj2.ptr (0x8000) → Heap: [0x8000]
→ Isolated memory blocks. Safe deterministic destruction.
deep_copy_mechanics.cpp
#include <iostream>
#include <cstring>

class Buffer {
private:
    char* data;
    size_t size;

public:
    // Dynamic Constructor: Allocates heap memory
    Buffer(const char* input) {
        size = std::strlen(input);
        data = new char[size + 1];
        std::strcpy(data, input);
        std::cout << "[Dynamic Constructor] Allocated " << size << " bytes.\n";
    }

    // User-Defined Deep Copy Constructor
    Buffer(const Buffer& other) {
        size = other.size;
        data = new char[size + 1];      // 1. Allocate distinct heap buffer
        std::strcpy(data, other.data);   // 2. Deep copy data contents
        std::cout << "[Deep Copy Constructor] Duplicated buffer into new address.\n";
    }

    // Destructor: Deallocates dynamically acquired heap memory
    ~Buffer() {
        delete[] data;
        data = nullptr;
        std::cout << "[Destructor] Deallocated buffer memory.\n";
    }

    void print() const {
        std::cout << "Buffer: " << data << " (Address: " << (void*)data << ")\n";
    }
};

int main() {
    Buffer b1("DevAscent Systems");
    b1.print();

    // Invoking Copy Constructor:
    Buffer b2 = b1; // Deep copy executed
    b2.print();     // Different address, identical data

    return 0; // Both destructors execute cleanly without double-free
}
03

Destructors & Construction/Destruction Sequencing (FIFO vs LIFO)

A destructor is a cleanup member function invoked automatically when an object's lifetime expires (when a stack object leaves scope or when a heap object is targeted by delete). It is prefixed by a tilde (~) and has zero parameters and zero return types.

Construction Sequence (FIFO)

Objects declared within the same scope are initialized in sequential order of declaration (First-In, First-Out):

Widget a; // 1. Constructs a
Widget b; // 2. Constructs b
Widget c; // 3. Constructs c
Destruction Sequence (LIFO)

When leaving scope, objects are destroyed in the exact reverse order of construction (Last-In, First-Out):

// Leaving block scope:
// 1. Destructs c
// 2. Destructs b
// 3. Destructs a
1
Scope Entry: Forward Construction Order
Stack builds frame: Resource A constructed → Resource B constructed → Resource C constructed.
2
Scope Termination (Closing Brace } or Return)
CPU triggers stack frame unwinding. Destruction begins at the topmost stack variable.
3
Reverse Destruction Order (LIFO)
~Resource C() runs → ~Resource B() runs → ~Resource A() runs. Zero resource leaks.
lifo_sequence_demonstration.cpp
#include <iostream>
#include <string>

class TraceScope {
private:
    std::string name;

public:
    TraceScope(std::string n) : name(std::move(n)) {
        std::cout << "++ CONSTRUCTING: " << name << "\n";
    }

    ~TraceScope() {
        std::cout << "-- DESTRUCTING:  " << name << "\n";
    }
};

int main() {
    std::cout << "--- Main Scope Start ---\n";
    TraceScope first("Scope_1");
    TraceScope second("Scope_2");

    {
        std::cout << "\n--- Inner Block Start ---\n";
        TraceScope innerA("Inner_A");
        TraceScope innerB("Inner_B");
        std::cout << "--- Inner Block End ---\n";
    } // innerB destroyed, then innerA destroyed

    std::cout << "\n--- Main Scope End ---\n";
    return 0; // second destroyed, then first destroyed
}
04

Viva Voce & Engineering Interview Questions

Q1 • Architecture What is RAII (Resource Acquisition Is Initialization), and how do constructors and destructors implement it?
RAII is an architectural paradigm where resource acquisition (memory allocation, file handles, socket bindings, mutex locks) is performed strictly inside a class constructor, and resource release is tied deterministically to the class destructor. Because stack objects have their destructors guaranteed to execute whenever leaving scope (even during exceptions), memory leaks and resource leaks are mathematically eliminated.
Q2 • Language Mechanics Why must the parameter of a Copy Constructor be passed by reference (const T&) instead of by value?
Passing an object by value creates a local copy on the stack, which itself requires invoking the copy constructor. If the copy constructor took its argument by value (T(T other)), calling the copy constructor would endlessly trigger another copy constructor call to pass the argument, causing an infinite recursive compile-time or runtime stack overflow.
Q3 • Memory Errors What is a "Double Free" error, and how does a user-defined deep copy constructor prevent it?
A Double Free occurs when two separate object instances point to the same dynamic heap address (the result of a shallow memberwise pointer copy). When each object goes out of scope, both destructors invoke delete[] on that exact same address, corrupting the heap allocator's metadata and crashing the process with SIGABRT. A deep copy constructor prevents this by allocating a brand-new independent heap buffer for the copy, ensuring each destructor frees its own isolated memory.
Q4 • Performance Why is a Member Initializer List faster than assignment inside the constructor body?
When assigning inside the constructor body (e.g., { member = val; }), the member is first default-constructed, and then immediately overwritten by calling its assignment operator, executing two distinct operations. A member initializer list (: member(val)) directly invokes the member's copy or move constructor once during its initial memory construction, eliminating the redundant default initialization pass.
Q5 • Rules & Defaults What happens to the compiler-generated default constructor when you declare a custom parameterized constructor?
The moment any user-defined constructor (such as a parameterized constructor) is declared in a class, the C++ compiler automatically suppresses and deletes its implicit default constructor. Consequently, attempting to instantiate an uninitialized object (Widget w;) results in a compile-time error unless the developer explicitly writes Widget() = default; or defines a default constructor manually.
Q6 • Sequencing Why are local stack objects destructed in the reverse order of their construction (LIFO)?
Local variables reside on the call stack frame, which naturally operates as a Last-In, First-Out (LIFO) stack data structure. Crucially, later objects frequently depend upon earlier objects (e.g., a file stream object initialized after a configuration object). Destroying objects in reverse order ensures that an object is never destroyed while another active object still depends on its resources.
Q7 • Systems Design Can a destructor be overloaded or take arguments in C++?
No; destructors can never take parameters, return values, or be overloaded. Because destructors are invoked implicitly by the runtime upon object expiration rather than explicitly by caller code, there is no syntactic mechanism to supply arguments or perform overload resolution during automatic stack unwinding.
Q8 • Modern Idioms What is the "Rule of Three" (and the modern C++ "Rule of Five")?
The Rule of Three states that if a class requires a user-defined Destructor, Copy Constructor, or Copy Assignment Operator, it almost certainly requires all three to correctly manage resources. In modern C++11, move semantics expands this to the Rule of Five, adding the Move Constructor and Move Assignment Operator for optimal zero-copy resource transfers.