⚙️ Unit 11 • C++ Systems Engineering

Inheritance & Class Hierarchy Architecture

Architectural code reuse and taxonomy modeling in C++: base and derived class subobject layouts, the 5 topologies of inheritance (single, multilevel, multiple, hierarchical, hybrid), access mode derivation matrices, constructor/destructor ordering chains, multiple inheritance ambiguity resolution, and virtual base classes solving the Diamond of Death.

🏛️ Hierarchy: Subobject Memory Composition
🛡️ Access Modes: public, protected, private Derivations
💎 Diamond Problem: virtual Base Tables
⏳ Sequence: Base First Construction → Derived First Destruction
01

Base and Derived Classes: Subobject Memory Layout

Inheritance establishes an "is-a" relationship between a general parent specification (Base Class) and a specialized child extension (Derived Class). In C++, inheritance is implemented through direct memory embedding: when an object of a derived class is allocated in RAM, the compiler physically constructs a complete Base Class Subobject directly inside the derived object's memory boundary.

Base Class (Parent / Superclass)

Defines foundational state members and reusable behavioral methods. In modern systems design, base classes should be designed for extension, exposing extension points through protected access.

Derived Class (Child / Subclass)

Inherits the state and behaviors of the base class while introducing additional specialized fields and operations. It can augment, override, or restrict inherited capabilities.

Derived Class Object Memory Footprint in RAM:
class Base { int b; }; class Derived : public Base { int d; };
Total Object Memory Boundary for Derived obj; (Address: 0x1000):
Base Subobject Offset [0x1000 - 0x1003]: int b = 10; (4 Bytes)
Derived Members Offset [0x1004 - 0x1007]: int d = 20; (4 Bytes)
A pointer to Derived* can be implicitly upcast to Base* with zero instruction overhead because the Base subobject begins at offset 0 of the Derived instance!
02

The 5 Topologies of Inheritance

C++ supports five architectural inheritance topologies to model diverse domain taxonomies:

1. Single Inheritance

One derived class inherits directly from exactly one base class:

class Dog : public Animal {};
2. Multilevel Inheritance

A chain of inheritance where a derived class acts as a base for another:

class C : public B {}; // B : public A
3. Multiple Inheritance

One derived class inherits from two or more distinct base classes:

class Device : public Radio, public Clock {};
4. Hierarchical Inheritance

Multiple derived classes branch out from a single common base class:

class Rectangle : public Shape {};
class Circle : public Shape {};
5. Hybrid (Multipath) Inheritance

A combination of two or more inheritance topologies in the same class hierarchy (e.g., Multiple + Multilevel), often giving rise to the Diamond problem.

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

// Base Entity
class NetworkNode {
protected:
    int nodeId;
public:
    NetworkNode(int id) : nodeId(id) {}
    int getId() const { return nodeId; }
};

// 1. Single Inheritance
class Router : public NetworkNode {
private:
    int routingTableEntries;
public:
    Router(int id, int entries) 
        : NetworkNode(id), routingTableEntries(entries) {}
    
    void printStatus() const {
        std::cout << "Router ID: " << nodeId 
                  << " | Routes: " << routingTableEntries << "\n";
    }
};

// 2. Multilevel Inheritance
class CoreBackboneRouter : public Router {
private:
    double throughputGbps;
public:
    CoreBackboneRouter(int id, int entries, double throughput)
        : Router(id, entries), throughputGbps(throughput) {}
    
    void printBackbone() const {
        printStatus();
        std::cout << "Throughput: " << throughputGbps << " Gbps\n";
    }
};

int main() {
    CoreBackboneRouter core(9001, 500000, 400.0);
    core.printBackbone();
    return 0;
}
03

Access Modes & The Derivation Access Matrix

When deriving a child class, the developer must specify an Access Mode (public, protected, or private). The access mode acts as an upper-bound filter on the visibility of inherited members in the derived class:

Base Class Member Access Derived via public Derived via protected Derived via private
public public in derived protected in derived private in derived
protected protected in derived protected in derived private in derived
private Inaccessible (Hidden) Inaccessible (Hidden) Inaccessible (Hidden)
Public Inheritance (Standard IS-A)

Preserves the public interface of the base class. The derived class can be used anywhere the base class is expected (Liskov Substitution Principle). This is standard C++ OOP practice.

Private / Protected Inheritance (HAS-A / Implemented-In-Terms-Of)

Hides the base interface from outside clients. Used when you want to reuse implementation details of a base class without exposing its public API or permitting public upcasting.

04

Constructor & Destructor Execution Order in Inheritance

Because a derived class depends upon its inherited base subobjects, C++ enforces a strict, deterministic sequence for construction and destruction across class hierarchies:

Constructor Ordering (Top-Down: Base First)
  1. Virtual Base classes are constructed first of all.
  2. Direct Base classes are constructed in the order they appear in the derivation list.
  3. Member objects of the derived class are constructed in declaration order.
  4. The Derived class constructor body executes last.
Destructor Ordering (Bottom-Up: Derived First)
  1. The Derived class destructor body executes first.
  2. Member objects of the derived class are destroyed in reverse declaration order.
  3. Direct Base classes are destroyed in reverse order of derivation.
  4. Virtual Base classes are destroyed last.
1
Phase 1: Construction Sequence (Base → Derived)
Base() initializes foundation state → Derived() initializes specialized members.
2
Phase 2: Lifetime & Execution
Object is fully valid and services client requests.
3
Phase 3: Destruction Sequence (Derived → Base)
~Derived() frees specialized resources → ~Base() cleans up foundation state safely.
constructor_destructor_chain.cpp
#include <iostream>

class BaseA {
public:
    BaseA()  { std::cout << "++ 1. BaseA Constructor\n"; }
    ~BaseA() { std::cout << "-- 4. BaseA Destructor\n"; }
};

class BaseB {
public:
    BaseB()  { std::cout << "++ 2. BaseB Constructor\n"; }
    ~BaseB() { std::cout << "-- 3. BaseB Destructor\n"; }
};

// Multiple Inheritance: Declaration order dictates constructor order!
class Derived : public BaseA, public BaseB {
public:
    Derived()  { std::cout << "++ 3. Derived Constructor\n"; }
    ~Derived() { std::cout << "-- 1. Derived Destructor\n"; }
};

int main() {
    {
        std::cout << "--- Creating Derived Instance ---\n";
        Derived d;
        std::cout << "--- Exiting Scope ---\n";
    }
    return 0;
}
05

Ambiguity in Multiple Inheritance & Virtual Base Classes

Multiple inheritance introduces two classic architectural failure modes: identifier collision ambiguity and the infamous Diamond Problem (Multipath Duplication):

1. Member Name Ambiguity

Occurs when two base classes declare a method with identical names (e.g., Printer::start() and Scanner::start()). Calling copier.start() triggers a compile-time ambiguity error.

Solution: Scope Resolution Operator: copier.Printer::start();

2. The Diamond of Death

Occurs when class D inherits from classes B and C, both of which inherit from common ancestor A. Without intervention, D contains two separate copies of A's subobject, causing massive memory bloat and compiler ambiguity on obj.a_member!

Memory Topologies: Standard vs Virtual Base Class:
Standard Multiple Inheritance:
Subobject B → [Copy 1 of Ancestor A]
Subobject C → [Copy 2 of Ancestor A]
Derived D Members
Duplicated subobject A. Invariant conflict!
With virtual Base Class:
Shared Single Ancestor Subobject A (Offset 0)
Subobject B (vbtptr to shared A)
Subobject C (vbtptr to shared A)
Derived D Members
Exactly ONE instance of A exists in RAM!
virtual_base_class_diamond.cpp
#include <iostream>

// Common Ancestor
class PowerUnit {
public:
    int voltage;
    PowerUnit(int v = 220) : voltage(v) {
        std::cout << "[PowerUnit] Initialized at " << voltage << "V\n";
    }
};

// Intermediate Bases inherit VIRTUALLY to prevent replication
class ProcessingUnit : virtual public PowerUnit {
public:
    ProcessingUnit() : PowerUnit(220) {}
};

class GraphicsUnit : virtual public PowerUnit {
public:
    GraphicsUnit() : PowerUnit(220) {}
};

// Most Derived Class: Responsibly constructs the shared virtual base
class GamingRig : public ProcessingUnit, public GraphicsUnit {
public:
    // With virtual inheritance, GamingRig calls PowerUnit constructor DIRECTLY:
    GamingRig(int v) : PowerUnit(v), ProcessingUnit(), GraphicsUnit() {
        std::cout << "[GamingRig] Online with unified voltage: " << voltage << "V\n";
    }
};

int main() {
    GamingRig rig(110);
    // Unambiguous direct access to voltage:
    std::cout << "System Voltage: " << rig.voltage << "V\n"; // 110V
    return 0;
}
💡 The Most-Derived Class Constructor Responsibility Rule

In a virtual inheritance hierarchy, intermediate classes (ProcessingUnit and GraphicsUnit) do NOT initialize the virtual base. The most-derived class (GamingRig) is solely and directly responsible for calling the constructor of the virtual base class (PowerUnit). If it does not explicitly invoke it, the virtual base class's default constructor is automatically invoked.

06

Viva Voce & Engineering Interview Questions

Q1 • Architecture What is the Diamond Problem in C++, and how does a virtual base class resolve it?
The Diamond Problem arises in multipath hybrid inheritance when two intermediate classes inherit from a common base class, and a derived class inherits from both. In non-virtual inheritance, the derived class inherits two separate copies of the original base subobject, creating ambiguity whenever accessing base members and wasting memory. Declaring intermediate inheritance as virtual public Base instructs the compiler to merge the subobjects into a single shared base instance referenced via internal virtual base table pointers (vbtptr).
Q2 • Access Control How does protected inheritance differ from public inheritance?
In public inheritance, base public members remain public in the derived class, preserving the IS-A relationship and permitting outside client access and public upcasting. In protected inheritance, all public and protected base members become protected inside the derived class; they remain accessible to subsequent child subclasses, but are completely hidden from outside clients, preventing the derived class from acting as a public substitute for the base class.
Q3 • Execution Sequence In what order are constructors and destructors executed in a multiple inheritance hierarchy?
Constructors are executed in the exact order that base classes are listed in the class declaration (left-to-right), with any virtual bases initialized before non-virtual bases, followed by member objects in declaration order, and finally the derived class constructor body. Destructors execute in the exact reverse order (LIFO): derived destructor body first, member objects reversed, non-virtual bases reversed (right-to-left), and virtual bases last.
Q4 • Ambiguity If two base classes contain a function with identical signatures (e.g., void display()), how does client code resolve the collision in a derived object?
The caller uses the Scope Resolution Operator (::) on the derived object instance to explicitly specify which base implementation to execute: derivedObj.BaseA::display(); or derivedObj.BaseB::display();. Alternatively, the derived class can explicitly override display() or use a using declaration (e.g., using BaseA::display;) to designate a default.
Q5 • Memory Model What overhead is introduced into an object's memory layout when using virtual inheritance?
Virtual inheritance introduces an invisible pointer inside intermediate subobjects—typically called a virtual base table pointer (vbtptr) or virtual table pointer (vptr). This pointer occupies 8 bytes (on 64-bit systems) and points to a table of offsets that allows the compiler to resolve the runtime position of the shared virtual base subobject, introducing a small memory footprint and a single pointer indirection lookup overhead.
Q6 • Language Rules Can a derived class directly access private members of its base class?
No; private members of a base class are strictly inaccessible to derived classes. They are physically inherited and present in the base subobject within the derived instance's memory layout, but the compiler forbids direct access by name. To allow derived classes to read or mutate base state while keeping it hidden from the general public, the base class must declare those members protected or provide protected accessor methods.
Q7 • Construction Chain In virtual inheritance, which class constructor is responsible for initializing the virtual base class?
The most-derived class is solely responsible for initializing the virtual base class. Intermediate derived classes in the hierarchy skip their explicit calls to the virtual base constructor; only the final, concrete class being instantiated invokes the virtual base constructor in its member initializer list.
Q8 • Modern Idioms What is the "Composition over Inheritance" design principle, and when should it be favored?
"Composition over Inheritance" recommends assembling complex systems out of discrete objects that contain references or values of other objects (HAS-A relationship) rather than subclassing (IS-A relationship). Composition minimizes tight coupling, avoids fragile base class issues, eliminates multiple inheritance complexities, and allows behaviors to be swapped dynamically at runtime rather than fixed rigidly at compile-time.