Inheritance & Polymorphism
Virtual Functions & Runtime Polymorphism
Base-class pointers to derived objects, why virtual changes which function runs, override, virtual destructors, and how the vtable makes it work.
The problem: static binding
A base-class pointer can point to a derived object. But by default, calling a function through it runs the base version — the compiler decides at compile time from the pointer's type. This is static (early) binding.
#include <iostream>
using namespace std;
class Animal {
public:
void speak() const { cout << "..." << endl; } // NOT virtual
};
class Dog : public Animal {
public:
void speak() const { cout << "Woof!" << endl; }
};
int main() {
Dog d;
Animal *p = &d; // base pointer, derived object
d.speak(); // Woof!
p->speak(); // "..." — chosen by the pointer type
return 0;
}Woof! ...
The fix: virtual functions
Mark the base function virtual. Now the function is chosen at run time from the actual object — dynamic (late) binding. This is runtime polymorphism.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Employee {
protected:
string name;
double base;
public:
Employee(const string &n, double b) : name(n), base(b) {}
virtual double pay() const { return base; }
virtual string role() const { return "Employee"; }
virtual ~Employee() {}
void payslip() const { cout << name << " (" << role() << "): Rs " << pay() << endl; }
};
class Manager : public Employee {
double bonus;
public:
Manager(const string &n, double b, double bo) : Employee(n, b), bonus(bo) {}
double pay() const override { return base + bonus; }
string role() const override { return "Manager"; }
};
class Intern : public Employee {
public:
Intern(const string &n) : Employee(n, 15000) {}
string role() const override { return "Intern"; } // keeps Employee::pay
};
int main() {
Manager m("Priya", 90000, 25000);
Intern i("Rahul");
Employee e("Sanjay", 60000);
vector<const Employee *> staff = {&e, &m, &i};
for (const Employee *s : staff) s->payslip(); // one loop, three behaviours
return 0;
}Sanjay (Employee): Rs 60000 Priya (Manager): Rs 115000 Rahul (Intern): Rs 15000
payslip() is written once in the base class, yet it calls the right pay() and role() for each object. Adding a new kind of employee requires no change to payslip — that is the real power of polymorphism.
Virtual destructors
If you delete a derived object through a base pointer and the base destructor is not virtual, only the base destructor runs — the derived part leaks.
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "~Base" << endl; } // remove 'virtual' and ~Derived is skipped
};
class Derived : public Base {
int *buffer = new int[1000];
public:
~Derived() { delete[] buffer; cout << "~Derived (freed buffer)" << endl; }
};
int main() {
Base *p = new Derived;
delete p; // runs ~Derived, then ~Base
return 0;
}~Derived (freed buffer) ~Base
How it works: the vtable
For each class with virtual functions, the compiler builds a virtual table (vtable): an array of pointers to that class's versions of the virtual functions. Each object gets a hidden pointer (the vptr) to its class's vtable. A virtual call follows object → vptr → vtable → function. That is one extra pointer lookup per call, and one extra pointer per object:
#include <iostream>
using namespace std;
class Plain { int x = 0; public: int f() { return x; } };
class Virtual { int x = 0; public: virtual int f() { return x; } };
int main() {
cout << "sizeof(Plain) = " << sizeof(Plain) << endl;
cout << "sizeof(Virtual) = " << sizeof(Virtual) << " (int + hidden vptr, padded)" << endl;
return 0;
}sizeof(Plain) = 4 sizeof(Virtual) = 16 (int + hidden vptr, padded)
| Static binding | Dynamic binding |
|---|---|
| decided at compile time | decided at run time |
| normal and overloaded functions | virtual functions called through pointers/references |
| faster | tiny overhead (vtable lookup) |
| also called early binding | also called late binding |
Test yourself
4 questions · pick an answer to check it instantly
- 1
What decides which function runs when a virtual function is called through a base pointer?
Virtual calls are dispatched at run time on the object's real type. - 2
What happens if a base class destructor is not virtual and you
deletea derived object through a base pointer?Technically it is undefined behaviour, and in practice the derived destructor is skipped. - 3
What is a vtable?
Each object's hidden vptr points to its class's vtable. - 4
What does
overridedo?It turns silent signature mistakes into compile errors.