Why must the base destructor be virtual?
Deleting through a base pointer calls the correct derived destructor—otherwise resource leaks occur.
Master C++ polymorphism: runtime polymorphism (virtual functions) and compile-time polymorphism (function overloading, templates). Learn with practical examples and best practices for OOP design.
Virtual Functions
Templates & Overloading
Dynamic Dispatch
Pure Virtual
Polymorphism lets one interface call different implementations. Master virtual functions, overrides, and when to use runtime polymorphism versus templates.
Virtual functions are classic interview material—object slicing, vtables, and override/final keywords appear constantly.
Polymorphism is a fundamental concept in Object-Oriented Programming (OOP) that allows objects of different types to be treated as objects of a common base type. The word "polymorphism" means "many forms" - it enables a single interface to represent different underlying forms (data types).
The following table compares different types of polymorphism in C++ with their characteristics and use cases:
| Type | Mechanism | When to Use | Characteristics |
|---|---|---|---|
| Runtime Polymorphism | Virtual Functions, Inheritance | When behavior depends on object type at runtime | Dynamic binding, uses vtable, slightly slower |
| Virtual Functions | virtual keyword | Base class defines interface, derived classes implement | Enables function overriding, late binding |
| Abstract Classes | Pure Virtual Functions | Define interfaces without implementation | Cannot instantiate, must be derived |
| Compile-time Polymorphism | Function Overloading, Templates | When behavior is known at compile time | Static binding, no runtime overhead |
| Function Overloading | Same name, different parameters | Multiple functions with similar purpose | Resolved at compile time |
| Operator Overloading | Overload operators like +, -, *, etc. | Make user-defined types act like built-in types | Enhances readability, follows conventions |
| Templates | Generic programming | Write type-independent code | Code reuse, type safety at compile time |
Runtime polymorphism is achieved through virtual functions and inheritance. The actual function called is determined at runtime based on the object's type.
class Base {
public:
virtual void show() { // Virtual function
cout << "Base class show" << endl;
}
};
class Derived : public Base {
public:
void show() override { // Override virtual function
cout << "Derived class show" << endl;
}
};
class Animal {
public:
virtual void sound() { cout << "Animal"; }
};
class Dog : public Animal {
public:
void sound() override { cout << "Woof"; }
};Animal* p = new Dog();
p->sound(); // Woof
delete p;class Shape {
public:
virtual double area() = 0;
};class Circle : public Shape {
double r;
public:
Circle(double x) : r(x) {}
double area() override { return 3.14*r*r; }
};vector shapes;
shapes.push_back(new Circle(2));
for (auto* s : shapes) cout << s->area(); class Base {
public:
virtual ~Base() {}
};
Base* p = new Derived();
delete p; // safe cleanupoverride keyword (C++11) for clarityfinal keyword to prevent further overridingCompile-time polymorphism includes function overloading, operator overloading, and templates. The function to call is determined during compilation.
void print(int x) {
cout << "Integer: " << x << endl;
}
void print(double x) {
cout << "Double: " << x << endl;
}
void print(string s) {
cout << "String: " << s << endl;
}
int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; }void print(string s) { cout << s; }Complex operator+(const Complex& o) {
return {real+o.real, imag+o.imag};
}template
T maximum(T a, T b) { return (a>b)?a:b; } Stack nums;
nums.push(10);
cout << nums.pop(); Use runtime polymorphism when behavior depends on object type at runtime. Use compile-time polymorphism when behavior is known at compile time and you want better performance.
Abstract classes cannot be instantiated and serve as base classes for other classes. They contain at least one pure virtual function.
class AbstractClass {
public:
virtual void pureVirtual() = 0; // Pure virtual function
virtual ~AbstractClass() = default;
};
class IPrintable {
public:
virtual void print() const = 0;
};class Doc : public IPrintable {
public:
void print() const override { cout << "Doc"; }
};class Shape {
public:
virtual double area() const = 0;
};class Square : public Shape {
double s;
public:
double area() const override { return s*s; }
};class IDatabase {
public:
virtual void connect() = 0;
};class MySQL : public IDatabase {
public:
void connect() override { cout << "MySQL"; }
};Understanding how C++ implements runtime polymorphism through virtual tables (vtables) and virtual pointers (vptrs).
class Base {
public:
virtual void f() { cout << "Base"; }
};class Derived : public Base {
public:
void f() override { cout << "Derived"; }
};Base* p = &derived;
p->f(); // Derivedp->nonVirtual(); // static bindingBase sliced = derived;
sliced.f(); // Base onlyclass V { virtual void f(); };
// object has hidden vptrAdvanced C++ polymorphism patterns including CRTP, type erasure, and visitor pattern.
template
class CRTPBase {
public:
void go() { static_cast(this)->impl(); }
}; class Circle : public Shape {
public:
void accept(Visitor& v) override { v.visit(*this); }
};variant v = 42;
cout << get(v); visit([](auto& x){ cout << x; }, v);TypeErasedPrinter p(3.14);
p.print();function task = [](){ cout << "run"; };
task(); override keyword (C++11) for clarityfinal when class shouldn't be derivedclass GoodBase {
public:
virtual ~GoodBase() {}
};void f() override { cout << "Derived"; }Derived d;
Base& ref = d;
ref.show(); // Derivedunique_ptr p = make_unique(); template
T add(T a,T b){ return a+b; } class BadBase {};
BadBase* p = new Derived();
delete p; // riskyDeleting through a base pointer calls the correct derived destructor—otherwise resource leaks occur.
Copying derived object into base by value loses derived parts—use pointers/references for polymorphism.
Declared with = 0; makes class abstract; derived classes must implement it.