README
Polymorphism in C++
Table of Contents
- •What is Polymorphism
- •Compile-time vs Runtime
- •Virtual Functions
- •How vtable Works (Under the Hood)
- •Override and Final
- •Pure Virtual and Abstract Classes
- •Virtual Destructor
- •Dynamic Casting
- •Best Practices
What is Polymorphism
Polymorphism = "many forms" - the ability of objects to take on different forms and behave differently based on their actual type, even when accessed through a common interface.
Shape* shape = new Circle(); // shape points to a Circle
shape->draw(); // Calls Circle::draw(), not Shape::draw() ✓
The Power of Polymorphism Visualized
┌─────────────────────────────────────────────────┐
│ Shape* shape │
│ (Pointer to Base Class) │
└───────────────────┬─────────────────────────────┘
│
│ Can point to ANY derived class
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Circle │ │ Rectangle │ │ Triangle │
│ ● │ │ ┌──────┐ │ │ △ │
│ │ │ │ │ │ │ / \ │
│ draw() ● │ │ draw() □ │ │ draw() △ │
└───────────┘ └───────────┘ └───────────┘
│ │ │
│ │ │
▼ ▼ ▼
"Drawing "Drawing "Drawing
a circle" a rectangle" a triangle"
Same Code: shape->draw();
Different Behavior: Based on actual object type at runtime
Types of Polymorphism
| Type | Also Called | Mechanism | When Resolved |
|---|---|---|---|
| Compile-time | Static Polymorphism | Function/Operator Overloading, Templates | Compile Time |
| Runtime | Dynamic Polymorphism | Virtual Functions + Inheritance | Runtime |
Compile-time vs Runtime
Compile-time (Static) Polymorphism
Resolved during compilation - the compiler knows exactly which function to call.
// 1. FUNCTION OVERLOADING
void print(int x) { cout << "Integer: " << x << endl; }
void print(double x) { cout << "Double: " << x << endl; }
void print(string s) { cout << "String: " << s << endl; }
print(42); // Calls print(int) - decided at compile time
print(3.14); // Calls print(double) - decided at compile time
print("hello"); // Calls print(string) - decided at compile time
// 2. TEMPLATES (Parametric Polymorphism)
template<typename T>
T add(T a, T b) { return a + b; }
add(1, 2); // Compiler generates add<int>()
add(1.5, 2.5); // Compiler generates add<double>()
// 3. OPERATOR OVERLOADING
class Vector2D {
public:
float x, y;
Vector2D operator+(const Vector2D& other) const {
return {x + other.x, y + other.y};
}
};
Runtime (Dynamic) Polymorphism
Resolved during program execution - uses virtual functions and inheritance.
class Animal {
public:
virtual void speak() { cout << "..." << endl; }
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};
class Cat : public Animal {
public:
void speak() override { cout << "Meow!" << endl; }
};
// The magic of runtime polymorphism:
void makeSpeak(Animal* animal) {
animal->speak(); // Which speak()? Decided at RUNTIME!
}
int main() {
Animal* dog = new Dog();
Animal* cat = new Cat();
makeSpeak(dog); // "Woof!" - determined by actual object type
makeSpeak(cat); // "Meow!" - determined by actual object type
delete dog;
delete cat;
}
Visual Comparison
┌────────────────────────────────────────────────────────────────────────────┐
│ COMPILE-TIME vs RUNTIME POLYMORPHISM │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ COMPILE-TIME (Static) │
│ ───────────────────── │
│ │
│ Source Code Compiled Binary │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ print(42) │ ────────► │ call print_int │ │
│ │ print(3.14) │ ────────► │ call print_double │ │
│ │ print("hi") │ ────────► │ call print_string │ │
│ └──────────────┘ └──────────────────────────┘ │
│ │
│ Decision made: During COMPILATION │
│ Pros: Fast (no runtime overhead) │
│ Cons: Less flexible, code must be known at compile time │
│ │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ RUNTIME (Dynamic) │
│ ──────────────── │
│ │
│ Source Code At Runtime │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ p->speak() │ │ Check p's vtable │ │
│ │ │ ────────► │ Look up speak() address │ │
│ │ │ │ Jump to correct function │ │
│ └──────────────┘ └──────────────────────────┘ │
│ │
│ Decision made: During EXECUTION │
│ Pros: Flexible, extensible, works with unknown types │
│ Cons: Slight overhead (vtable lookup) │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Virtual Functions
The virtual keyword enables runtime polymorphism by telling the compiler to use dynamic dispatch.
Syntax and Basic Example
class Base {
public:
virtual void method() { // ◄── 'virtual' keyword
cout << "Base::method()" << endl;
}
virtual ~Base() = default; // Always make destructor virtual!
};
class Derived : public Base {
public:
void method() override { // ◄── 'override' keyword (C++11)
cout << "Derived::method()" << endl;
}
};
// Polymorphic behavior in action
Base* ptr = new Derived();
ptr->method(); // Output: "Derived::method()" ✓
delete ptr;
Non-virtual vs Virtual: The Difference
class Base {
public:
void nonVirtual() { cout << "Base::nonVirtual" << endl; }
virtual void isVirtual() { cout << "Base::isVirtual" << endl; }
};
class Derived : public Base {
public:
void nonVirtual() { cout << "Derived::nonVirtual" << endl; }
void isVirtual() override { cout << "Derived::isVirtual" << endl; }
};
Base* ptr = new Derived();
ptr->nonVirtual(); // "Base::nonVirtual" ◄── Static binding (pointer type)
ptr->isVirtual(); // "Derived::isVirtual" ◄── Dynamic binding (object type)
delete ptr;
┌────────────────────────────────────────────────────────────────────────┐
│ STATIC vs DYNAMIC BINDING │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Base* ptr = new Derived(); │
│ │ │ │
│ │ └─── Actual object type: Derived │
│ │ │
│ └─── Pointer type: Base* │
│ │
│ NON-VIRTUAL (Static Binding): │
│ ───────────────────────────── │
│ ptr->nonVirtual() │
│ │ │
│ └── Compiler sees: "ptr is Base*" │
│ Decision: Call Base::nonVirtual() │
│ When: Compile time │
│ │
│ VIRTUAL (Dynamic Binding): │
│ ────────────────────────── │
│ ptr->isVirtual() │
│ │ │
│ └── Runtime sees: "Object is actually Derived" │
│ Decision: Call Derived::isVirtual() │
│ When: Runtime (via vtable lookup) │
│ │
└────────────────────────────────────────────────────────────────────────┘
How vtable Works (Under the Hood)
Every class with virtual functions has a hidden pointer called vptr (virtual pointer) that points to a vtable (virtual function table).
vtable Structure
┌─────────────────────────────────────────────────────────────────────────────┐
│ VTABLE MECHANISM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ class Base { BASE VTABLE │
│ virtual void foo(); ┌──────────────────────────────┐ │
│ virtual void bar(); │ [0] → Base::foo() address │ │
│ }; │ [1] → Base::bar() address │ │
│ └──────────────────────────────┘ │
│ │
│ class Derived : public Base { DERIVED VTABLE │
│ void foo() override; ┌──────────────────────────────┐ │
│ // bar() not overridden │ [0] → Derived::foo() address │◄─── │
│ }; │ [1] → Base::bar() address │ │
│ └──────────────────────────────┘ │
│ Changed! │ │
│ │ │
│ │ │
│ OBJECT MEMORY LAYOUT │ │
│ ───────────────────── │ │
│ │ │
│ Base object: │ │
│ ┌─────────────────────┐ │ │
│ │ vptr ──────────────────► Base vtable │ │
│ │ Base data members │ │ │
│ └─────────────────────┘ │ │
│ │ │
│ Derived object: │ │
│ ┌─────────────────────┐ │ │
│ │ vptr ──────────────────► Derived vtable ──────────────┘ │
│ │ Base data members │ │
│ │ Derived data members│ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Virtual Function Call Step-by-Step
Base* ptr = new Derived();
ptr->foo();
STEP-BY-STEP EXECUTION:
══════════════════════
Step 1: Access object through ptr
┌──────────────────────────────────────┐
│ ptr ──────────► Derived Object │
│ ┌──────────────┐ │
│ │ vptr ────┐ │ │
│ │ data │ │ │
│ └──────────────┘ │
└──────────────────────────────────────┘
Step 2: Follow vptr to vtable
┌──────────────────────────────────────┐
│ │ │
│ ▼ │
│ Derived vtable │
│ ┌──────────────────┐ │
│ │[0] Derived::foo()│ │
│ │[1] Base::bar() │ │
│ └──────────────────┘ │
└──────────────────────────────────────┘
Step 3: Look up function at index 0 (foo's slot)
┌──────────────────────────────────────┐
│ │ │
│ ▼ │
│ Derived::foo() ◄── Call this! │
│ │ │
│ ▼ │
│ Execute function code │
└──────────────────────────────────────┘
Complete Example with Memory Layout
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { cout << "..." << endl; }
virtual void move() { cout << "Moving" << endl; }
virtual ~Animal() = default;
protected:
int age = 0;
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
// move() is NOT overridden - uses Animal::move()
private:
string breed;
};
class Cat : public Animal {
public:
void speak() override { cout << "Meow!" << endl; }
void move() override { cout << "Prowling" << endl; } // Overrides!
};
MEMORY LAYOUT AND VTABLES:
Animal vtable: Dog vtable: Cat vtable:
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ [0] Animal::speak │ │ [0] Dog::speak │ │ [0] Cat::speak │
│ [1] Animal::move │ │ [1] Animal::move │ │ [1] Cat::move │
│ [2] Animal::~dtor │ │ [2] Dog::~dtor │ │ [2] Cat::~dtor │
└────────────────────┘ └────────────────────┘ └────────────────────┘
Animal object: Dog object: Cat object:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ vptr ───────────┼──► │ vptr ───────────┼──► │ vptr ───────────┼──►
│ age: 0 │ │ age: 0 │ │ age: 0 │
└─────────────────┘ │ breed: "" │ └─────────────────┘
└─────────────────┘
Code:
Animal* animals[] = {new Animal, new Dog, new Cat};
for (auto a : animals) {
a->speak(); // "..." → "Woof!" → "Meow!"
a->move(); // "Moving" → "Moving" → "Prowling"
}
Override and Final
The override Keyword (C++11)
The override keyword tells the compiler to verify that the function actually overrides a virtual function from the base class. This catches bugs at compile time!
class Base {
public:
virtual void foo() { }
virtual void bar(int x) { }
void baz() { } // NOT virtual
};
class Derived : public Base {
public:
void foo() override { } // ✅ OK - correctly overrides Base::foo
// void foo() const override { } // ❌ ERROR: signature mismatch (const)
// void bar() override { } // ❌ ERROR: signature mismatch (no int)
// void baz() override { } // ❌ ERROR: Base::baz is not virtual
// void qux() override { } // ❌ ERROR: no Base::qux to override
};
┌─────────────────────────────────────────────────────────────────────────────┐
│ WHY USE override? - CATCHES BUGS! │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ WITHOUT override: │
│ ───────────────── │
│ │
│ class Base { │
│ virtual void process(int x) { } │
│ }; │
│ │
│ class Derived : public Base { │
│ void process(double x) { } // OOPS! Different parameter type │
│ }; // Creates NEW function, doesn't override │
│ // Compiles without warning! │
│ │
│ WITH override: │
│ ────────────── │
│ │
│ class Derived : public Base { │
│ void process(double x) override { } // ❌ COMPILER ERROR! │
│ }; // "does not override" │
│ │
│ BEST PRACTICE: Always use 'override' when overriding virtual functions │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The final Keyword (C++11)
Prevents further overriding of a virtual function or inheritance from a class.
// 1. Final on a virtual function - cannot be overridden further
class Base {
public:
virtual void foo() { }
};
class Derived : public Base {
public:
void foo() final { } // ◄── No class can override this anymore
};
class MoreDerived : public Derived {
public:
// void foo() override { } // ❌ ERROR: foo is final in Derived
};
// 2. Final on a class - cannot be inherited from
class Singleton final { // ◄── No class can inherit from Singleton
public:
static Singleton& getInstance();
};
// class BadIdea : public Singleton { }; // ❌ ERROR: Singleton is final
Visual Decision Guide
┌─────────────────────────────────────────────────────────────────┐
│ WHEN TO USE override AND final │
├─────────────────────────────────────────────────────────────────┤
│ │
│ override: │
│ ────────── │
│ ✅ ALWAYS use when you intend to override │
│ ✅ Catches typos and signature mismatches │
│ ✅ Makes code self-documenting │
│ │
│ final on function: │
│ ────────────────── │
│ ✅ When override behavior must not change in subclasses │
│ ✅ When further overriding would break invariants │
│ ✅ Can enable compiler optimizations (devirtualization) │
│ │
│ final on class: │
│ ─────────────── │
│ ✅ Singletons and similar patterns │
│ ✅ Classes not designed for inheritance │
│ ✅ Security-critical classes │
│ ✅ Can enable compiler optimizations │
│ │
└─────────────────────────────────────────────────────────────────┘
Pure Virtual and Abstract Classes
Pure Virtual Function
class Shape {
public:
virtual void draw() = 0; // Pure virtual
virtual double area() = 0;
};
// Shape s; // ERROR: cannot instantiate abstract class
Abstract Class
class Animal {
public:
virtual void speak() = 0;
virtual void move() = 0;
void breathe() { // Can have regular methods
cout << "Breathing..." << endl;
}
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
void move() override { cout << "Running" << endl; }
};
Interface Pattern
class IDrawable {
public:
virtual void draw() = 0;
virtual ~IDrawable() = default;
};
class IClickable {
public:
virtual void onClick() = 0;
virtual ~IClickable() = default;
};
class Button : public IDrawable, public IClickable {
public:
void draw() override { /* ... */ }
void onClick() override { /* ... */ }
};
Virtual Destructor
Why It's Essential
class Base {
public:
~Base() { cout << "~Base" << endl; } // Non-virtual: PROBLEM!
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() { delete[] data; cout << "~Derived" << endl; }
};
Base* ptr = new Derived();
delete ptr; // Only calls ~Base! Memory leak!
The Fix
class Base {
public:
virtual ~Base() { cout << "~Base" << endl; }
};
class Derived : public Base {
public:
~Derived() override { cout << "~Derived" << endl; }
};
Base* ptr = new Derived();
delete ptr; // Calls ~Derived then ~Base ✓
Rule: If a class has any virtual functions, make destructor virtual.
Dynamic Casting
Syntax
Derived* d = dynamic_cast<Derived*>(basePtr);
if (d) {
// Cast succeeded
}
Example
class Animal { public: virtual ~Animal() {} };
class Dog : public Animal { public: void bark() { cout << "Woof!"; } };
class Cat : public Animal { public: void meow() { cout << "Meow!"; } };
void handleAnimal(Animal* a) {
if (Dog* d = dynamic_cast<Dog*>(a)) {
d->bark();
} else if (Cat* c = dynamic_cast<Cat*>(a)) {
c->meow();
}
}
References
try {
Derived& d = dynamic_cast<Derived&>(baseRef);
} catch (bad_cast& e) {
cout << "Cast failed" << endl;
}
Best Practices
✅ Do
// 1. Use virtual destructor
class Base { public: virtual ~Base() = default; };
// 2. Use override keyword
void method() override {}
// 3. Use pure virtual for interfaces
virtual void required() = 0;
// 4. Prefer references/pointers for polymorphism
void process(Shape& shape);
❌ Don't
// 1. Call virtual functions in constructor/destructor
Base() { virtualMethod(); } // Calls Base version!
// 2. Slice objects
Derived d;
Base b = d; // Slices - loses Derived part
// 3. Forget virtual destructor
class Base { ~Base() {} }; // Missing virtual
Quick Reference
class Base {
public:
virtual void method() {} // Virtual
virtual void pure() = 0; // Pure virtual
virtual ~Base() = default; // Virtual destructor
};
class Derived : public Base {
public:
void method() override {} // Override
void pure() override {} // Must implement
void final_m() final {} // Cannot override
};
// Polymorphic usage
Base* ptr = new Derived();
ptr->method(); // Calls Derived::method
delete ptr; // Proper cleanup
// Dynamic cast
if (auto* d = dynamic_cast<Derived*>(ptr)) {
// Use d
}
Compile & Run
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples