Polymorphism in C++
Polymorphism means "many forms." In C++, it allows the same function name or operator to behave differently depending on the context. It is one of the most powerful features of OOP and enables flexible, extensible program design.
Types of Polymorphism in C++
| Type | Also Known As | Resolved At |
|---|---|---|
| Compile-time (Static) | Function/Operator Overloading | Compile time |
| Runtime (Dynamic) | Virtual Functions / Overriding | Runtime |
Compile-Time Polymorphism — Function Overloading
Multiple functions can share the same name but differ in the number or type of parameters:
#include <iostream>
using namespace std;
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
int main() {
cout << add(3, 4) << endl; // 7
cout << add(2.5, 3.5) << endl; // 6.0
cout << add(1, 2, 3) << endl; // 6
return 0;
}
Output:
7
6
6Runtime Polymorphism — Virtual Functions
Runtime polymorphism is achieved through virtual functions and function overriding. When a base class pointer or reference calls a virtual function, C++ decides at runtime which version to call based on the actual object type.
Without Virtual Function (wrong behavior):
class Shape {
public:
void draw() { cout << "Drawing Shape" << endl; }
};
class Circle : public Shape {
public:
void draw() { cout << "Drawing Circle" << endl; }
};
int main() {
Shape *ptr = new Circle();
ptr->draw(); // Calls Shape::draw — NOT Circle::draw!
return 0;
}
Output (unexpected):
Drawing ShapeWith Virtual Function (correct behavior):
class Shape {
public:
virtual void draw() { cout << "Drawing Shape" << endl; }
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing Circle" << endl; }
};
class Square : public Shape {
public:
void draw() override { cout << "Drawing Square" << endl; }
};
int main() {
Shape *ptr;
ptr = new Circle();
ptr->draw(); // Calls Circle::draw
ptr = new Square();
ptr->draw(); // Calls Square::draw
return 0;
}
Output:
Drawing Circle
Drawing SquareThe override Keyword
Using override explicitly tells the compiler that the function is intentionally overriding a virtual function. It catches typos and mismatches at compile time.
Pure Virtual Functions and Abstract Classes
A pure virtual function has no implementation in the base class. A class with at least one pure virtual function becomes an abstract class — it cannot be instantiated directly.
class Animal {
public:
virtual void speak() = 0; // pure virtual function
};
class Cat : public Animal {
public:
void speak() override { cout << "Meow!" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};
int main() {
Animal *a;
a = new Cat();
a->speak(); // Meow!
a = new Dog();
a->speak(); // Woof!
return 0;
}
Output:
Meow!
Woof!Virtual Destructor
When using base class pointers with derived objects, the destructor should be virtual to ensure the correct destructor is called:
class Base {
public:
virtual ~Base() { cout << "Base destroyed" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived destroyed" << endl; }
};
int main() {
Base *b = new Derived();
delete b; // Calls Derived::~Derived() then Base::~Base()
return 0;
}
Output:
Derived destroyed
Base destroyedKey Takeaways
- Polymorphism allows the same interface to behave differently for different types.
- Compile-time polymorphism is achieved through function overloading.
- Runtime polymorphism is achieved through virtual functions and overriding.
- Mark base class functions with
virtualto enable runtime dispatch. - Use
overridein derived classes for safety and clarity. - Abstract classes with pure virtual functions define interfaces that subclasses must implement.
- Always make the base class destructor
virtualwhen using polymorphism.
