Lesson 4: Foundations of Inheritance & Constructor Lifecycle

Lesson Overview

This lesson covers the fundamentals of Inheritance—how C++ allows you to reuse, extend, and specialize existing code, how to control access using visibility modes, and how the compiler coordinates the initialization and destruction of parent and child subobjects in memory.


🚀 1. The “Why” & Concept Breakdown

Inheritance is the mechanism by which a derived class {specialized child} acquires the attributes and behaviors of an existing base class {generalized parent}. It directly implements the DRY (Don’t Repeat Yourself) principle and models the “Is-A” relationship (e.g., a Student is a Person).

graph BT
    subgraph Specialization ["Derived / Specialized Classes"]
        D1["Student<br>(Adds GPA, Roll)"]
        D2["Teacher<br>(Adds Subject, Salary)"]
    end
    
    subgraph Generalization ["Base / Generalized Class"]
        B["Person<br>(name, id, age)"]
    end
    
    D1 -->|Inherits : public| B
    D2 -->|Inherits : public| B

⚡ Inheritance: Python vs. C++

  • Syntax & Modes: class Child : public Parent (must specify : public mode; defaults to private).
  • Constructor Delegation: Invokes parent constructor via Initializer List: Child(...) : Parent(args), childVar(val) {} (equivalent to Python’s super().__init__()).
  • Access Levels: Base private members are inaccessible to children. Use protected for members that child classes need direct access to.

Key Architectural Principles

  1. Generalization {grouping shared fields into a parent} vs. Specialization {adding unique fields in a child}.
  2. The protected Access Specifier:
    • private members cannot be accessed directly by derived classes (though they occupy memory in the derived object’s subobject {the nested parent memory chunk}).
    • public members are accessible everywhere.
    • protected members behave like private variables to outside main() code, but are directly accessible inside derived classes.

Inheritance Visibility Modes

When deriving a class (class Child : visibility_mode Parent), the visibility mode sets the upper ceiling for inherited access:

Base Class MemberPublic Derivation (: public)Protected Derivation (: protected)Private Derivation (: private / default)
PrivateInaccessible (Not Inherited)Inaccessible (Not Inherited)Inaccessible (Not Inherited)
ProtectedBecomes ProtectedBecomes ProtectedBecomes Private
PublicBecomes PublicBecomes ProtectedBecomes Private

🔑 2. Keyword & Syntax Dictionary

Keyword / SymbolMeaningPractical Syntax Example
:Separator denoting inheritance.class Student : public Person { ... };
protectedAccess modifier granting access to derived classes only.protected: int roll_number;
: Base(...)Initializer list syntax calling a parameterized parent constructor from the derived class.Student(string n, int r) : Person(n), roll(r) {}

💻 3. Step-by-Step Code Evolution

Step 4.1: Public vs. Private Derivation

#include <iostream>
#include <string>
using namespace std;
 
class Person {
protected:
    string name; // Accessible in derived classes
private:
    int id;      // Inaccessible to derived classes
 
public:
    void setDetails(string n, int i) {
        name = n;
        id = i;
    }
    void displayBase() const {
        cout << "ID: " << id << " | Name: " << name << endl;
    }
};
 
// 1. Public Derivation: Public members stay public
class Student : public Person {
private:
    float gpa;
public:
    void setStudent(string n, int i, float g) {
        setDetails(n, i); // Using public base setter
        name = n;         // Direct access to protected member
        gpa = g;
    }
    void displayStudent() const {
        cout << "Student: " << name << " | GPA: " << gpa << endl;
    }
};
 
// 2. Private Derivation: Public base members become private
class Employee : private Person {
private:
    float salary;
public:
    void setEmployee(string n, int i, float s) {
        setDetails(n, i);
        salary = s;
    }
    void displayEmployee() const {
        displayBase(); // Permitted internally
        cout << "Salary: $" << salary << endl;
    }
};
 
int main() {
    Student s;
    s.setStudent("Alice", 101, 3.9);
    s.displayBase();    // ALLOWED: Public in Student
 
    Employee e;
    e.setEmployee("Bob", 202, 75000);
    // e.displayBase(); // COMPILE ERROR: displayBase is private inside Employee
    e.displayEmployee(); // ALLOWED
 
    return 0;
}

Step 4.2: Constructor & Destructor Execution Order in Hierarchies

When a derived class object is created and destroyed:

  1. Constructors execute top-down (Base Derived).
  2. Destructors execute bottom-up (Derived Base).
sequenceDiagram
    autonumber
    participant main as main() Scope
    participant Grandfather
    participant Father
    participant Child

    main->>Grandfather: 1. Construct Grandfather Subobject
    Grandfather->>Father: 2. Construct Father Subobject
    Father->>Child: 3. Construct Child Subobject
    Note over Child: Object active in memory
    main->>Child: 4. Destroy Child (Exit Scope)
    Child->>Father: 5. Destroy Father Subobject
    Father->>Grandfather: 6. Destroy Grandfather Subobject
#include <iostream>
using namespace std;
 
class Grandfather {
public:
    Grandfather() { cout << "[+] Grandfather Constructor" << endl; }
    ~Grandfather() { cout << "[-] Grandfather Destructor" << endl; }
};
 
class Father : public Grandfather {
public:
    Father() { cout << "[+] Father Constructor" << endl; }
    ~Father() { cout << "[-] Father Destructor" << endl; }
};
 
class Child : public Father {
public:
    Child() { cout << "[+] Child Constructor" << endl; }
    ~Child() { cout << "[-] Child Destructor" << endl; }
};
 
int main() {
    cout << "--- Instantiating Child ---" << endl;
    {
        Child c;
    }
    cout << "--- Child Exited Scope ---" << endl;
    return 0;
}

Console Output:

--- Instantiating Child ---
[+] Grandfather Constructor
[+] Father Constructor
[+] Child Constructor
[-] Child Destructor
[-] Father Destructor
[-] Grandfather Destructor
--- Child Exited Scope ---

🧠 4. Under-the-Hood Memory Layout

Object of Class 'Student' in Memory:
+-----------------------------------------------------------+
|  [Base Class Subobject: Person]                            |
|  +-----------------------------------------------------+  |
|  | string name (inherited protected field)            |  |
|  | int id      (inherited private field)               |  |
|  +-----------------------------------------------------+  |
+-----------------------------------------------------------+
|  [Derived Class Native Data]                              |
|  +-----------------------------------------------------+  |
|  | float gpa   (Student's own private field)          |  |
|  +-----------------------------------------------------+  |
+-----------------------------------------------------------+

⚠️ 5. The Debugger’s Guide (Common Traps)

Trap 1: Base Class Lacks Default Constructor

class Parent {
public:
    Parent(int x) {} // No default constructor!
};
class Child : public Parent {
public:
    Child() {} // ❌ ERROR: no matching function for call to 'Parent::Parent()'
};
  • The Fix: Explicitly pass parameters to the base constructor in the child’s initializer list: Child(int val) : Parent(val) {}.

Trap 2: Direct Private Access Attempt

Attempting to read/write a private base variable in a child function triggers a compiler error. Use protected in the base class or call public base getters/setters.


📝 6. Practice Quiz

Q1. If class B : A {} is declared with no visibility mode, what is the default?

  • A. public
  • B. protected
  • C. private
  • D. virtual

Q2. Which base members can be directly accessed in derived classes but remain hidden from main()?

  • A. private
  • B. protected
  • C. public
  • D. Static private

Q3. What is the execution order of destructors in multi-level inheritance?

  • A. Base first, then derived.
  • B. Derived first, then base.
  • C. Random based on memory layout.
  • D. Destructors execute simultaneously.

Quiz Answers

  1. C (class defaults to private inheritance).
  2. B (protected access specifier).
  3. B (Destructors execute in reverse order of construction: Derived Base).

💬 7. Viva Quick-Prep

Q1: Can constructors and destructors be inherited in C++?

Answer: No. Constructors and destructors are never inherited. Each class must define or rely on compiler generation for its own constructor and destructor.

Q2: Why is a base constructor invoked before a derived constructor?

Answer: Because a derived object is built on top of the base subobject. The base portion must be fully constructed and initialized in memory before the derived constructor runs to ensure safe access to parent properties.

Q3: How do we resolve a naming collision if base and derived classes define identical function names?

Answer: The derived function overrides/hides the base function. To call the base version from an object of the derived class, qualify it with the scope resolution operator: object.BaseClass::function().