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.
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.
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.
Inherits the state and behaviors of the base class while introducing additional specialized fields and operations. It can augment, override, or restrict inherited capabilities.
class Base { int b; }; class Derived : public Base { int d; };Derived obj; (Address: 0x1000):int b = 10; (4 Bytes)
int d = 20; (4 Bytes)
Derived* can be implicitly upcast to Base* with zero instruction overhead because the Base subobject begins at offset 0 of the Derived instance!
C++ supports five architectural inheritance topologies to model diverse domain taxonomies:
One derived class inherits directly from exactly one base class:
class Dog : public Animal {};
A chain of inheritance where a derived class acts as a base for another:
class C : public B {}; // B : public A
One derived class inherits from two or more distinct base classes:
class Device : public Radio, public Clock {};
Multiple derived classes branch out from a single common base class:
class Rectangle : public Shape {};
class Circle : public Shape {};
A combination of two or more inheritance topologies in the same class hierarchy (e.g., Multiple + Multilevel), often giving rise to the Diamond problem.
#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;
}
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) |
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.
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.
Because a derived class depends upon its inherited base subobjects, C++ enforces a strict, deterministic sequence for construction and destruction across class hierarchies:
Base() initializes foundation state → Derived() initializes specialized members.~Derived() frees specialized resources → ~Base() cleans up foundation state safely.#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;
}
Multiple inheritance introduces two classic architectural failure modes: identifier collision ambiguity and the infamous Diamond Problem (Multipath Duplication):
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();
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!
virtual Base Class:#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;
}
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.
virtual base class resolve it?
virtual public Base instructs the compiler to merge the subobjects into a single shared base instance referenced via internal virtual base table pointers (vbtptr).
protected inheritance differ from public inheritance?
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.
void display()), how does client code resolve the collision in a derived object?
::) 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.
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.
private members of its base class?
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.