Module 03: Inheritance
Goal: Master all forms of inheritance, the diamond problem, virtual inheritance, and best practices. Time: 2 days of focused study Prerequisites: Module 01-02
Table of Contents
- What is Inheritance?
- Types of Inheritance
- Constructor & Destructor Order
- Method Overriding
- The Diamond Problem
- Virtual Inheritance
overrideandfinalKeywords- Object Slicing
- Inheritance vs Composition
- Common Mistakes
- Practice Problems
- Interview Questions
1. What is Inheritance?
Inheritance lets a class (derived/child) acquire the properties and behavior of another class (base/parent).
Real-World Analogy:
VEHICLE (Base class)
├─ has: color, speed, fuelCapacity
├─ can: start(), stop(), refuel()
│
├── CAR (Derived class)
│ ├─ inherits everything from Vehicle
│ ├─ adds: numDoors, trunkSize
│ └─ adds: openTrunk()
│
├── MOTORCYCLE (Derived class)
│ ├─ inherits everything from Vehicle
│ ├─ adds: hasSidecar
│ └─ adds: wheelie()
│
└── TRUCK (Derived class)
├─ inherits everything from Vehicle
├─ adds: payloadCapacity
└─ adds: loadCargo()
All share Vehicle's data and behavior, but each adds its own specialization.Basic Syntax
#include <iostream>
#include <string>
using namespace std;
// Base class (Parent / Superclass)
class Animal {
protected:
string name;
int age;
public:
Animal(string n, int a) : name(n), age(a) {
cout << "Animal constructor: " << name << endl;
}
void eat() { cout << name << " is eating" << endl; }
void sleep() { cout << name << " is sleeping" << endl; }
string getName() const { return name; }
int getAge() const { return age; }
virtual ~Animal() { cout << "Animal destructor: " << name << endl; }
};
// Derived class (Child / Subclass)
class Dog : public Animal { // public inheritance = "Dog IS-A Animal"
string breed;
public:
// Must call base constructor in initializer list
Dog(string n, int a, string b) : Animal(n, a), breed(b) {
cout << "Dog constructor: " << name << endl;
}
void bark() { cout << name << " says: Woof!" << endl; }
string getBreed() const { return breed; }
~Dog() { cout << "Dog destructor: " << name << endl; }
};
int main() {
Dog d("Buddy", 3, "Labrador");
d.eat(); // ✅ Inherited from Animal
d.sleep(); // ✅ Inherited from Animal
d.bark(); // ✅ Dog's own method
// Dog IS-A Animal — can be used wherever Animal is expected
Animal* ptr = &d; // ✅ Polymorphism
ptr->eat(); // ✅ Calls Animal::eat()
// ptr->bark(); // ❌ Animal doesn't know about bark()
}2. Types of Inheritance
Single Inheritance
[Animal]
▲
│
[Dog]
One parent, one child.class Animal { /* ... */ };
class Dog : public Animal { /* ... */ };Multilevel Inheritance
[Animal]
▲
│
[Mammal]
▲
│
[Dog]
Chain of inheritance — Dog inherits from Mammal, which inherits from Animal.class Animal {
public:
void breathe() { cout << "Breathing" << endl; }
};
class Mammal : public Animal {
public:
void feedMilk() { cout << "Feeding milk" << endl; }
};
class Dog : public Mammal {
public:
void bark() { cout << "Woof!" << endl; }
};
int main() {
Dog d;
d.breathe(); // ✅ From Animal (through Mammal)
d.feedMilk(); // ✅ From Mammal
d.bark(); // ✅ Own method
}Hierarchical Inheritance
[Shape]
/ | \
[Circle] [Rect] [Triangle]
One parent, multiple children.class Shape {
protected:
string color;
public:
Shape(string c) : color(c) {}
virtual double area() const = 0;
};
class Circle : public Shape {
double radius;
public:
Circle(string c, double r) : Shape(c), radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(string c, double w, double h) : Shape(c), w(w), h(h) {}
double area() const override { return w * h; }
};Multiple Inheritance
[Flyable] [Swimmable]
\ /
\ /
[Duck]
One child inherits from multiple parents.class Flyable {
public:
void fly() { cout << "Flying" << endl; }
};
class Swimmable {
public:
void swim() { cout << "Swimming" << endl; }
};
class Duck : public Flyable, public Swimmable {
public:
void quack() { cout << "Quack!" << endl; }
};
int main() {
Duck d;
d.fly(); // ✅ From Flyable
d.swim(); // ✅ From Swimmable
d.quack(); // ✅ Own method
}Java equivalent: Java does NOT support multiple inheritance of classes (to avoid diamond problem). You can only
extendsone class. But you canimplementsmultiple interfaces.
Hybrid Inheritance (Combination)
[Animal]
/ \
[Mammal] [WingedAnimal]
\ /
[Bat]
Causes the DIAMOND PROBLEM (covered next).3. Constructor & Destructor Order
class A {
public:
A() { cout << "A constructed" << endl; }
~A() { cout << "A destroyed" << endl; }
};
class B : public A {
public:
B() { cout << "B constructed" << endl; }
~B() { cout << "B destroyed" << endl; }
};
class C : public B {
public:
C() { cout << "C constructed" << endl; }
~C() { cout << "C destroyed" << endl; }
};
int main() {
C obj;
}
/*
Output:
A constructed ← Base first (top-down)
B constructed
C constructed
C destroyed ← Derived first (bottom-up — reverse order!)
B destroyed
A destroyed
RULE:
Constructors: Base → Derived (parent builds first)
Destructors: Derived → Base (child cleans up first)
Think: Build foundation first, demolish roof first.
*/Multiple Inheritance Constructor Order
class A { public: A() { cout << "A"; } };
class B { public: B() { cout << "B"; } };
class C : public A, public B { // Order in class declaration!
public:
C() { cout << "C"; }
};
int main() {
C obj; // Output: ABC
// Order is determined by the ORDER OF INHERITANCE DECLARATION
// NOT the order in the initializer list!
}4. Method Overriding
class Animal {
public:
virtual void speak() const {
cout << "..." << endl;
}
virtual void describe() const {
cout << "I am an animal" << endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
// OVERRIDE — replace base class behavior
void speak() const override {
cout << "Woof!" << endl;
}
void describe() const override {
Animal::describe(); // Call base class version first!
cout << "Specifically, I am a dog" << endl;
}
};
class Cat : public Animal {
public:
void speak() const override {
cout << "Meow!" << endl;
}
};
int main() {
Dog d;
Cat c;
Animal* animals[] = {&d, &c};
for (auto* a : animals) {
a->speak(); // Calls the CORRECT override (polymorphism)
a->describe();
}
d.describe();
// Output:
// I am an animal
// Specifically, I am a dog
}Hiding vs Overriding
class Base {
public:
virtual void foo(int x) { cout << "Base::foo(int)" << endl; }
void bar() { cout << "Base::bar()" << endl; }
};
class Derived : public Base {
public:
void foo(int x) override { cout << "Derived::foo(int)" << endl; }
// ⚠️ This HIDES Base::bar, not overrides (bar is not virtual!)
void bar() { cout << "Derived::bar()" << endl; }
};
int main() {
Derived d;
Base* bp = &d;
bp->foo(1); // "Derived::foo(int)" ← virtual dispatch (override)
bp->bar(); // "Base::bar()" ← static dispatch (hiding!)
d.bar(); // "Derived::bar()" ← called directly on Derived
// HIDING: base function is hidden, but called through base pointer
// it uses the base version! Only virtual functions override.
}5. The Diamond Problem
[Animal]
/ \
[Mammal] [Bird]
\ /
[Bat]
BAT inherits from both MAMMAL and BIRD.
Both MAMMAL and BIRD inherit from ANIMAL.
PROBLEM: Bat has TWO copies of Animal!
Bat::Mammal::Animal ← copy 1
Bat::Bird::Animal ← copy 2
bat.eat() — Which Animal::eat()? AMBIGUOUS!class Animal {
public:
int weight;
void eat() { cout << "Animal eating" << endl; }
};
class Mammal : public Animal {
public:
void breathe() { cout << "Mammal breathing" << endl; }
};
class Bird : public Animal {
public:
void layEggs() { cout << "Bird laying eggs" << endl; }
};
class Bat : public Mammal, public Bird {
public:
void fly() { cout << "Bat flying" << endl; }
};
int main() {
Bat b;
// b.eat(); // ❌ AMBIGUOUS! Which Animal::eat()?
// b.weight; // ❌ AMBIGUOUS! Which Animal::weight?
// Workaround (ugly): explicitly specify the path
b.Mammal::eat(); // ✅ Calls Mammal's Animal::eat()
b.Bird::eat(); // ✅ Calls Bird's Animal::eat()
b.Mammal::weight = 100; // Sets Mammal's copy
b.Bird::weight = 200; // Sets Bird's copy — DIFFERENT copies!
cout << sizeof(Bat) << endl; // Contains TWO Animal sub-objects!
}6. Virtual Inheritance
Virtual inheritance solves the diamond problem by ensuring only ONE copy of the base class exists.
class Animal {
public:
int weight;
Animal() : weight(0) { cout << "Animal()" << endl; }
Animal(int w) : weight(w) { cout << "Animal(" << w << ")" << endl; }
void eat() { cout << "Animal eating, weight=" << weight << endl; }
};
// VIRTUAL inheritance — share a single Animal instance
class Mammal : virtual public Animal {
public:
Mammal() { cout << "Mammal()" << endl; }
};
class Bird : virtual public Animal {
public:
Bird() { cout << "Bird()" << endl; }
};
class Bat : public Mammal, public Bird {
public:
// With virtual inheritance, the MOST DERIVED class must initialize
// the virtual base class!
Bat() : Animal(50) { // ← Bat initializes Animal directly
cout << "Bat()" << endl;
}
};
int main() {
Bat b;
b.eat(); // ✅ No ambiguity! Only ONE Animal
b.weight = 75; // ✅ Only ONE weight field
cout << sizeof(Bat) << endl; // Smaller than without virtual
}
/*
Output:
Animal(50) ← Called ONCE (from Bat's initializer)
Mammal()
Bird()
Bat()
WITHOUT virtual inheritance: Animal would be constructed TWICE.
WITH virtual inheritance: Animal is constructed ONCE by the most derived class.
Memory layout:
Without virtual: [Animal | Mammal data] [Animal | Bird data] [Bat data]
With virtual: [Mammal data] [Bird data] [Bat data] [Animal] ← shared
vbptr ──────────────────────────────────►
*/How Virtual Inheritance Works Internally
Without virtual inheritance:
┌──────────────────────┐
│ Mammal::Animal::weight│ ← copy 1
│ Mammal data │
│ Bird::Animal::weight │ ← copy 2
│ Bird data │
│ Bat data │
└──────────────────────┘
With virtual inheritance:
┌──────────────────────┐
│ vbptr (→ Animal) │ ← Mammal's virtual base pointer
│ Mammal data │
│ vbptr (→ Animal) │ ← Bird's virtual base pointer
│ Bird data │
│ Bat data │
│ ═══════════════════ │
│ Animal::weight │ ← SINGLE shared copy
└──────────────────────┘
Each virtual base gets a vbptr (virtual base pointer) that points
to the shared base class sub-object. This adds overhead:
- Extra pointer per virtual base class
- Slightly slower access (pointer indirection)7. override and final
override (C++11) — Safety Net
class Base {
public:
virtual void foo(int x) const {}
virtual void bar() {}
virtual ~Base() = default;
};
class Derived : public Base {
public:
// Without override — COMPILES but creates a NEW function (hiding bug!)
// void foo(int x) {} // Missing const! Creates separate function!
// With override — CATCHES the bug at compile time
// void foo(int x) override {} // ❌ ERROR: doesn't match any virtual function in Base
void foo(int x) const override {} // ✅ Correctly overrides
void bar() override {} // ✅ Correctly overrides
};
// ALWAYS use override when overriding virtual functions!
// It catches:
// - Typos in function names
// - Wrong parameter types
// - Missing const
// - Base function not being virtualfinal (C++11) — Prevent Further Extension
// final on a class — cannot be inherited from
class Singleton final {
// No class can inherit from Singleton
};
// class Derived : public Singleton {}; // ❌ ERROR!
// final on a virtual function — cannot be overridden further
class Animal {
public:
virtual void breathe() { cout << "Breathing" << endl; }
virtual ~Animal() = default;
};
class Mammal : public Animal {
public:
void breathe() override final { // Can be overridden here, but NO FURTHER
cout << "Mammal breathing with lungs" << endl;
}
};
class Dog : public Mammal {
// void breathe() override {} // ❌ ERROR! breathe() is final in Mammal
};8. Object Slicing
class Animal {
public:
string name;
virtual void speak() const { cout << "..." << endl; }
};
class Dog : public Animal {
public:
string breed;
void speak() const override { cout << "Woof!" << endl; }
};
int main() {
Dog d;
d.name = "Buddy";
d.breed = "Labrador";
// ---- OBJECT SLICING ----
Animal a = d; // COPIES Dog into Animal — Dog-specific data is LOST!
a.speak(); // "..." ← Calls Animal::speak, NOT Dog::speak!
// a.breed; // ❌ ERROR — breed was sliced off!
// The Dog part (breed, Dog's vtable) was "sliced" away.
// Only the Animal part was copied.
// ---- CORRECT: Use pointers or references ----
Animal* ptr = &d;
ptr->speak(); // "Woof!" ← Correct polymorphic behavior!
Animal& ref = d;
ref.speak(); // "Woof!" ← Correct!
}
/*
Object Slicing Visualized:
Dog object: [name | vtable_ptr(Dog) | breed]
↓ copy to Animal
Animal object: [name | vtable_ptr(Animal)]
↑ breed is gone!
↑ vtable is now Animal's!
RULE: Never pass polymorphic objects by VALUE.
Always use pointers or references.
*/Preventing Object Slicing
class Animal {
public:
virtual void speak() const = 0;
virtual ~Animal() = default;
// Delete copy constructor and assignment to prevent slicing
Animal(const Animal&) = delete;
Animal& operator=(const Animal&) = delete;
protected:
Animal() = default; // Only derived classes can construct
};9. Inheritance vs Composition
INHERITANCE: "IS-A" relationship
Dog IS-A Animal
Circle IS-A Shape
COMPOSITION: "HAS-A" relationship
Car HAS-A Engine
House HAS-A Room
Person HAS-A Address// ❌ BAD — Using inheritance for "HAS-A"
class Engine {
public:
void start() { cout << "Engine started" << endl; }
};
class Car : public Engine { // Car IS-A Engine? NO!
// Exposes engine.start() as car.start() — weird
};
// ✅ GOOD — Using composition for "HAS-A"
class Car {
Engine engine; // Car HAS-A Engine
public:
void start() {
engine.start(); // Delegates to Engine
cout << "Car is ready to drive" << endl;
}
};WHEN TO USE INHERITANCE:
✅ True "IS-A" relationship (Dog IS-A Animal)
✅ You need polymorphism (treat derived as base)
✅ The base class is designed for extension (has virtual functions)
WHEN TO USE COMPOSITION:
✅ "HAS-A" relationship (Car HAS-A Engine)
✅ You want flexibility (can swap components at runtime)
✅ You want to avoid tight coupling
✅ When in doubt — PREFER COMPOSITION
Rule of thumb: "Prefer composition over inheritance"
(We'll dive deeper into this in Module 07)10. Common Mistakes
// ❌ MISTAKE 1: Forgetting virtual destructor in base class
class Base {
~Base() {} // NOT virtual!
};
class Derived : public Base {
int* data = new int[100];
~Derived() { delete[] data; }
};
// Base* p = new Derived(); delete p; ← Derived destructor NEVER called! LEAK!
// ❌ MISTAKE 2: Not calling base constructor
class Base {
int x;
public:
Base(int val) : x(val) {} // No default constructor!
};
class Derived : public Base {
public:
// Derived() {} // ❌ ERROR! Must call Base(int)
Derived() : Base(0) {} // ✅ Explicitly call base constructor
};
// ❌ MISTAKE 3: Slicing objects
void process(Animal a) { // Pass by VALUE — slices!
a.speak(); // Always calls Animal::speak, never the override
}
// ✅ Fix: void process(const Animal& a) or void process(Animal* a)
// ❌ MISTAKE 4: Using inheritance for code reuse only
class Stack : public vector<int> {}; // Stack IS-A vector? NO!
// Inherits push_back, insert, erase — Stack shouldn't expose these!
// ✅ Fix: Composition — Stack HAS-A vector<int>
// ❌ MISTAKE 5: Diamond problem without virtual inheritance
class A {};
class B : public A {}; // Should be: virtual public A
class C : public A {}; // Should be: virtual public A
class D : public B, public C {}; // Two copies of A!11. Practice Problems
Problem 1: Employee Hierarchy
class Employee {
protected:
string name;
int id;
double baseSalary;
public:
Employee(string n, int i, double s) : name(n), id(i), baseSalary(s) {}
virtual double calculatePay() const { return baseSalary; }
virtual void displayInfo() const {
cout << "ID: " << id << " | Name: " << name
<< " | Pay: $" << calculatePay() << endl;
}
virtual ~Employee() = default;
};
class Manager : public Employee {
double bonus;
int teamSize;
public:
Manager(string n, int i, double s, double b, int ts)
: Employee(n, i, s), bonus(b), teamSize(ts) {}
double calculatePay() const override { return baseSalary + bonus; }
void displayInfo() const override {
Employee::displayInfo();
cout << " Team size: " << teamSize << endl;
}
};
class Intern : public Employee {
int hoursWorked;
double hourlyRate;
public:
Intern(string n, int i, double rate, int hours)
: Employee(n, i, 0), hourlyRate(rate), hoursWorked(hours) {}
double calculatePay() const override { return hourlyRate * hoursWorked; }
};Problem 2: Solve the Diamond Problem
// Create a class hierarchy:
// Device → Laptop (virtual)
// Device → Tablet (virtual)
// Laptop + Tablet → Convertible
// Device has: powerOn(), powerOff(), batteryLevel
// Ensure only ONE copy of Device exists in Convertible
class Device {
protected:
string brand;
int batteryLevel;
public:
Device(string b) : brand(b), batteryLevel(100) {
cout << "Device(" << brand << ")" << endl;
}
void powerOn() { cout << brand << " powered on" << endl; }
void powerOff() { cout << brand << " powered off" << endl; }
virtual ~Device() = default;
};
class Laptop : virtual public Device {
public:
Laptop(string b) : Device(b) { cout << "Laptop()" << endl; }
void type() { cout << "Typing on laptop" << endl; }
};
class Tablet : virtual public Device {
public:
Tablet(string b) : Device(b) { cout << "Tablet()" << endl; }
void touchDraw() { cout << "Drawing on tablet" << endl; }
};
class Convertible : public Laptop, public Tablet {
public:
Convertible(string b) : Device(b), Laptop(b), Tablet(b) {
cout << "Convertible()" << endl;
}
};
// Usage:
// Convertible c("Lenovo");
// c.powerOn(); ✅ No ambiguity
// c.type(); ✅ From Laptop
// c.touchDraw(); ✅ From Tablet12. Interview Questions
Q1: What is inheritance? What are the types?
Answer: Inheritance is a mechanism where a derived class acquires properties and behaviors from a base class. Types: (1) Single — one parent, one child. (2) Multiple — one child, multiple parents. (3) Multilevel — chain (A→B→C). (4) Hierarchical — one parent, multiple children. (5) Hybrid — combination (can cause diamond problem). C++ supports all types; Java doesn't support multiple class inheritance.
Q2: What is the diamond problem? How does C++ solve it?
Answer: When a class inherits from two classes that both inherit from a common base, the derived class gets TWO copies of the base. This causes ambiguity when accessing base members. C++ solves it with virtual inheritance — class B : virtual public A. Virtual inheritance ensures only ONE shared copy of the base exists. The most-derived class must initialize the virtual base directly.
Q3: What is the order of constructor and destructor calls?
Answer: Constructors are called top-down: base first, then derived. Destructors are called in reverse: derived first, then base. With multiple inheritance, the order follows the declaration order in the class definition. Virtual base classes are constructed before non-virtual bases.
Q4: What is object slicing?
Answer: When a derived class object is assigned to a base class object by value, the derived-specific data is "sliced off." Only the base part is copied, and the vtable pointer reverts to the base class. Polymorphism is lost. Prevention: always use pointers or references for polymorphic objects, never pass by value.
Q5: What is the difference between method overriding and method hiding?
Answer: Overriding replaces a virtual function in a derived class. Dynamic dispatch ensures the correct version is called through a base pointer. Hiding occurs when a derived class declares a non-virtual function with the same name as a base function — the base version is hidden but still called through a base pointer. Always use virtual and override to ensure overriding, not hiding.
Q6: When should you use override and final?
Answer: Always use override when overriding virtual functions — it catches bugs at compile time (wrong signature, missing const, non-virtual base function). Use final on a class to prevent inheritance or on a virtual function to prevent further overriding. final enables compiler optimizations (devirtualization).
Q7: Can a constructor be virtual?
Answer: No. Constructors cannot be virtual because the vtable doesn't exist yet during construction — it's being set up. However, you can achieve "virtual construction" using the Factory Method pattern (virtual clone or create methods).
Q8: Can a destructor be pure virtual?
Answer: Yes! A pure virtual destructor makes the class abstract. But you MUST still provide a definition (outside the class), because derived destructors implicitly call it: Base::~Base() {}. This is the only pure virtual function that requires a body.
Q9: What is public, protected, and private inheritance?
Answer: They control how base members appear in the derived class. public: keeps access levels. protected: public→protected. private: everything→private. Public inheritance models "IS-A" (Dog IS-A Animal). Private inheritance models "implemented-in-terms-of" (Stack uses vector internally but isn't a vector).
Q10: Inheritance vs Composition — when to use which?
Answer: Use inheritance for true "IS-A" relationships where polymorphism is needed. Use composition for "HAS-A" relationships. Prefer composition — it's more flexible, avoids tight coupling, and allows runtime component swapping. Inheritance creates a rigid hierarchy. Composition is a "has-a" relationship that's easier to change and test.
Q11: What is virtual inheritance's overhead?
Answer: Virtual inheritance adds a virtual base pointer (vbptr) per virtual base class — typically 8 bytes on 64-bit systems. Access to virtual base members requires pointer indirection (slightly slower). Construction is more complex — the most-derived class must initialize virtual bases. The trade-off is worth it when the diamond problem exists.
Q12: Can you prevent a class from being inherited?
Answer: Yes, using the final keyword: class Singleton final { ... };. In older C++, you could use a private constructor with a friend factory, but final is the clean modern approach.
Next Module: 04 - Polymorphism — Compile-time vs runtime polymorphism, virtual functions, vtable, operator overloading.