03-inheritance.md

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

  1. What is Inheritance?
  2. Types of Inheritance
  3. Constructor & Destructor Order
  4. Method Overriding
  5. The Diamond Problem
  6. Virtual Inheritance
  7. override and final Keywords
  8. Object Slicing
  9. Inheritance vs Composition
  10. Common Mistakes
  11. Practice Problems
  12. 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 extends one class. But you can implements multiple 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 virtual

final (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 Tablet

12. 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.