C++ Inheritance, Virtual Functions & Runtime Polymorphism Masterclass

โšก Modern C++ (C++17 / C++20 / C++23) ๐ŸŸข Lesson 11 ๐Ÿ“‚ Phase 11: Inheritance & Polymorphism ๐Ÿ“… 2026 Master Edition
๐Ÿ“Œ Covered in this in-depth guide: Base & Derived Classes ยท public/protected/private Inheritance ยท Virtual Functions ยท vtable ยท Pure Virtual ยท Abstract Classes ยท virtual Destructor ยท dynamic_cast ยท Composition vs Inheritance

Welcome to Phase 11: Inheritance & Polymorphism! Inheritance lets a derived class reuse, extend, and specialize a base class. Polymorphism (runtime dispatch through virtual functions) allows a base-class pointer/reference to call the correct overridden method at runtime โ€” the foundation of object-oriented design in C++.

1Inheritance Basics โ€” Base & Derived Classes

A derived class inherits members from a base class. Access specifier in the inheritance declaration controls how base members are seen in the derived class and outside world.

Inheritance Typepublic in Baseprotected in Baseprivate in Base
public inheritancepublicprotectedinaccessible
protected inheritanceprotectedprotectedinaccessible
private inheritanceprivateprivateinaccessible

Inheritance Hierarchy Types:

โ€ข Single: One derived from one base.

โ€ข Multilevel: A โ†’ B โ†’ C chain.

โ€ข Multiple: Derived inherits from two or more bases.

โ€ข Hierarchical: Multiple derived classes from one base.

2Virtual Functions & Runtime Polymorphism

Declaring a method virtual in the base class tells the compiler to use the vtable (virtual dispatch table) mechanism. At runtime, the actual object type determines which override is called โ€” this is runtime polymorphism.

Key Rules:

โ€ข Always mark the destructor virtual in a polymorphic base class to prevent undefined behaviour on delete.

โ€ข Use override keyword in derived classes to catch typos at compile time.

โ€ข Use final to prevent further overriding.

โ€ข A pure virtual function (= 0) makes the class abstract โ€” it cannot be instantiated directly.

Virtual Table (vtable) mechanism: Animal object (base pointer) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ vptr โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ–บ Animal::vtable โ”‚ data members... โ”‚ โ”‚โ”€โ”€ &Animal::sound() โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚โ”€โ”€ &Animal::~Animal() Dog object (actual runtime type) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ vptr โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ–บ Dog::vtable โ”‚ data members... โ”‚ โ”‚โ”€โ”€ &Dog::sound() โ† OVERRIDE โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚โ”€โ”€ &Dog::~Dog() animal.sound() โ†’ vptr lookup โ†’ Dog::sound() called โœ…
C++ โ€” Polymorphism, Virtual & Pure Virtualโ–ถ Run in Compiler
#include <iostream>
#include <memory>
#include <vector>

class Animal {
public:
    virtual void sound() const {
        std::cout << "Generic animal sound
";
    }
    virtual std::string name() const = 0;  // pure virtual โ†’ abstract class
    virtual ~Animal() = default;           // virtual destructor ESSENTIAL!
};

class Dog : public Animal {
public:
    void sound() const override {
        std::cout << "Dog: Woof! Woof!
";
    }
    std::string name() const override { return "Dog"; }
};

class Cat : public Animal {
public:
    void sound() const override {
        std::cout << "Cat: Meow!
";
    }
    std::string name() const override { return "Cat"; }
};

class GuideDog : public Dog {        // multilevel inheritance
public:
    void guide() const {
        std::cout << "GuideDog: Leading the way!
";
    }
};

int main() {
    // Runtime polymorphism via base-class pointer
    std::vector<std::unique_ptr<Animal>> animals;
    animals.push_back(std::make_unique<Dog>());
    animals.push_back(std::make_unique<Cat>());
    animals.push_back(std::make_unique<GuideDog>());

    for (const auto& a : animals) {
        std::cout << a->name() << ": ";
        a->sound();
    }

    // dynamic_cast for safe downcasting
    Animal* ptr = new GuideDog();
    if (auto* gd = dynamic_cast<GuideDog*>(ptr)) {
        gd->guide();
    }
    delete ptr;
    return 0;
}
3Upcasting, Downcasting & dynamic_cast
Cast TypeDirectionSafetyExample
UpcastingDerived โ†’ BaseAlways safe (implicit)Animal* a = &dog;
Downcasting (static)Base โ†’ DerivedUnsafe โ€” programmer must verify typestatic_cast<Dog*>(a)
Downcasting (dynamic)Base โ†’ DerivedRuntime-checked; returns nullptr if wrongdynamic_cast<Dog*>(a)

Composition vs Inheritance:

โ€ข Inheritance expresses an IS-A relationship (Dog IS-A Animal).

โ€ข Composition expresses a HAS-A relationship (Car HAS-A Engine). Prefer composition for flexibility โ€” it avoids deep inheritance hierarchies and fragile base-class coupling.

4Abstract Classes & Interface-Style Design

In C++, an abstract class has at least one pure virtual function. It defines a contract that all derived classes must fulfill. This mimics Java/C# interfaces without a separate interface keyword.

C++ โ€” Abstract Interface Patternโ–ถ Run in Compiler
#include <iostream>

class ILogger {                     // Interface-style abstract base
public:
    virtual void log(const std::string& msg) const = 0;
    virtual void setLevel(int level) = 0;
    virtual ~ILogger() = default;
};

class ConsoleLogger : public ILogger {
    int level_{0};
public:
    void log(const std::string& msg) const override {
        std::cout << "[LOG-" << level_ << "] " << msg << "
";
    }
    void setLevel(int level) override { level_ = level; }
};

void processWithLogger(ILogger& logger) {
    logger.setLevel(2);
    logger.log("System started successfully");
}

int main() {
    ConsoleLogger clog;
    processWithLogger(clog);
    return 0;
}
5Technical FAQs

Q1: Why is virtual destructor important?

Without a virtual destructor, deleting a Derived object through a Base pointer calls only the Base destructor โ€” the Derived destructor is skipped, causing resource leaks.

Q2: What is the diamond problem in multiple inheritance?

When two base classes share a common ancestor, the derived class gets two copies of that ancestor. Solved with virtual inheritance: class B : virtual public A.

Q3: Can a constructor be virtual?

No. Constructors cannot be virtual because the vtable is set up during construction โ€” the object type isn't fully known yet.

Q4: What is the overhead of virtual functions?

Each polymorphic object carries a hidden vptr (8 bytes on 64-bit). Each virtual call does one extra pointer dereference โ€” negligible in almost all applications.

Q5: What does override keyword do?

override instructs the compiler to verify the function actually overrides a virtual function in the base. It catches typos and signature mismatches at compile time.