Polymorphism in C++
Polymorphism means "many forms" — the ability for the same call to do different things depending on the actual object behind it. In C++ it comes in two flavours: compile-time polymorphism (function and operator overloading, chosen by the compiler) and run-time polymorphism (virtual functions, chosen while the program runs). This guide focuses on the run-time kind, which is the engine of flexible OOP design.
Here is the idea in one example.
Run-time polymorphism with virtual functions
Mark a base function virtual, override it in derived classes, and call it through a base-class reference or pointer. C++ then dispatches to the real object's version.
#include <iostream>
#include <memory>
#include <vector>
class Shape {
public:
virtual double area() const = 0; // pure virtual: must be overridden
virtual ~Shape() = default; // virtual destructor for safe cleanup
};
class Circle : public Shape {
double r;
public:
Circle(double radius) : r(radius) {}
double area() const override { return 3.14159 * r * r; }
};
class Square : public Shape {
double side;
public:
Square(double s) : side(s) {}
double area() const override { return side * side; }
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
shapes.push_back(std::make_unique<Square>(3.0));
// same call, different behaviour per object
for (const auto& s : shapes) {
std::cout << "area = " << s->area() << "\n";
}
}
Output:
area = 12.5664
area = 9
The loop never asks "is this a circle or a square?" — area() resolves to the correct override automatically. That is run-time polymorphism.
How it works: the virtual keyword
Without virtual, a call through a Shape* would always run Shape::area. Adding virtual tells the compiler to look up the function in a hidden table (the vtable) attached to each object, so the call follows the object's true type.
Always use override
The override keyword asks the compiler to verify that you really are overriding a base virtual function. If the signature does not match, you get a compile error instead of a silent bug.
double area() const override { return side * side; } // checked at compile time
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesAlways give polymorphic base classes a virtual destructor
If you delete a derived object through a base pointer and the base destructor is not virtual, only the base part is cleaned up — undefined behaviour. Declaring virtual ~Shape() = default; fixes this. (Smart pointers like unique_ptr rely on it too.)
Compile-time polymorphism, briefly
Function overloading and templates are resolved by the compiler before the program runs:
void print(int n) { /* ... */ }
void print(double d) { /* ... */ } // overload chosen at compile time
This is faster (no vtable lookup) but fixed: the choice is locked in when you build.
Choosing between the two
Use run-time polymorphism when the set of types can grow and you want existing code to work with new ones unchanged — a plugin system, a list of UI widgets, different payment methods. Adding a new Shape does not touch the loop that calls area(). Use compile-time polymorphism when the types are known in advance and you want maximum speed with no indirection — generic algorithms and numeric code lean this way.
A practical signal: if you find yourself storing different concrete types together in one container and operating on them uniformly, you almost certainly want virtual functions and a common base class. If instead you are writing one routine that should work for many types but each call site knows its exact type, overloading or templates fit better.
Common mistakes
- Forgetting
virtualon the base function. Without it, calls through a base pointer ignore the override entirely. - Omitting the virtual destructor. Deleting a derived object via a base pointer then leaks or corrupts memory.
- Slicing. Copying a derived object into a base value (
Shape s = circle;) chops off the derived part. Use references or pointers for polymorphic code. - Mismatched signatures. A subtly different parameter list creates a new function instead of an override —
overridecatches this.
FAQ
What is the difference between compile-time and run-time polymorphism? Compile-time (overloading, templates) is resolved when you build the program; run-time (virtual functions) is resolved while it executes, based on the object's actual type.
What is a pure virtual function?
A virtual function set to = 0. It has no body in the base class and forces every concrete derived class to provide one. A class with at least one is abstract and cannot be instantiated.
Keep learning
- Build the foundation with Inheritance in C++.
- Go deeper into the mechanics in Virtual Functions & Abstract Classes.
- See the full series on the C++ OOP hub.
Master OOP design with mentors in Jalgaon — join the waitlist for our C++ Programming course.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFounder, Infoplanet
Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.
Related guides
Classes & Objects in C++
Understand C++ classes and objects — defining a class, creating objects, member functions, and access control with public and private members.
Constructors & Destructors in C++
Understand C++ constructors and destructors — how objects are initialised and cleaned up, including parameterised, copy constructors, and initializer lists.
Encapsulation & Abstraction in C++
Understand encapsulation and abstraction in C++ — hiding internal data, exposing controlled interfaces, and why both make code safer and easier to change.
