C++: Polymorphism
Last updated: 2026-08-26
In Lesson 31, we learned about inheritance — letting derived classes reuse base class code.
But in real-world scenarios, you need to use a base class pointer to uniformly handle different derived class objects.
This is polymorphism — "one interface, multiple implementations."
1. What Is Polymorphism?
(1) 1.1 Polymorphism in Everyday Life
| Real-Life Example | Program Equivalent |
|---|---|
| You tell a dog to "speak," it barks | Calling speak() on a base class pointer, but executing the derived class version |
| You tell a cat to "speak," it meows | The same function call produces different behavior for different objects |
The essence of polymorphism: Using a base class pointer to point to a derived class object, so that calling a virtual function dynamically binds to the derived class implementation.
(2) 1.2 Why Do We Need Polymorphism?
Without polymorphism (tedious):
▶ Example 2: Object-Oriented Programming Demo (Difficulty ⭐)
#include <iostream>
class Dog {
public:
void speak() { std::cout << "Woof!" << std::endl; }
};
class Cat {
public:
void speak() { std::cout << "Meow!" << std::endl; }
};
int main() {
Dog* d = new Dog();
Cat* c = new Cat();
d->speak();
c->speak();
// ❌ Have to handle different types separately
return 0;
}
Output:
Woof!
Meow!
With polymorphism (concise):
#include <iostream>
class Animal {
public:
virtual void speak() { std::cout << "Animal speaks" << std::endl; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Woof!" << std::endl; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "Meow!" << std::endl; }
};
int main() {
Animal* animals = {new Dog(), new Cat()};
for (int i = 0; i < 2; i++) {
animals[i]->speak(); // ✅ Polymorphism: automatically calls the correct version
}
return 0;
}
2. Virtual Functions (virtual)
(1) 2.1 Basic Usage
Declare a function as virtual in the base class, and override it in the derived class.
#include <iostream>
#include <string>
class Animal {
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
// Declare as virtual function
virtual void speak() {
std::cout << name << " makes a sound." << std::endl;
}
// Base class should also have a virtual destructor!
virtual ~Animal() {}
};
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
void speak() override {
std::cout << name << " says: Woof!" << std::endl;
}
};
int main() {
Animal* a = new Dog("Buddy");
a->speak(); // ✅ Output: Buddy says: Woof!
delete a; // ✅ Will call Dog's destructor first, then Animal's
return 0;
}
⚠️ Key Point: When a base class has virtual functions, the destructor must also be declared virtual! Otherwise, delete on a base class pointer won't call the derived class's destructor.
(2) 2.2 The override Keyword (C++11)
C++11 introduced the override keyword, which lets the compiler check whether you're actually overriding a virtual function.
class Dog : public Animal {
public:
// ✅ With override, the compiler will check
void speak() override { ... }
// ❌ If the signature is wrong (e.g., void speak(int x)), the compiler will error
// void speak(int x) override { ... } // Compile error: no function to override
};
💡 Recommendation: Always use override — let the compiler catch bugs for you.
3. Pure Virtual Functions and Abstract Classes
(1) 3.1 What Is a Pure Virtual Function?
A pure virtual function is a virtual function that is only declared but not implemented in the base class, marked with = 0.
class Animal {
public:
// Pure virtual function
virtual void speak() = 0;
virtual ~Animal() {}
};
A class with pure virtual functions is called an "abstract class" — it cannot be instantiated and can only serve as a base class.
Animal a; // ❌ Compile error: cannot instantiate abstract class
Animal* p = new Dog("Buddy"); // ✅ Can use a base class pointer to point to a derived class
(2) 3.2 Why Do We Need Pure Virtual Functions?
To force derived classes to override the function.
class Animal {
public:
virtual void speak() = 0; // Force derived classes to implement
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
// ✅ If you forget to override speak(), the compiler will error
};
4. How Virtual Functions Work (Optional)
(1) 4.1 Virtual Function Table (vtable)
C++ implements polymorphism using a virtual function table (vtable):
- For classes with virtual functions, the compiler generates a
vtable(a table storing virtual function addresses) - Each object contains a hidden
vptr(a pointer to the vtable) - When a virtual function is called, the correct function address is found through
vptr
💡 Tip: This is why objects of classes with virtual functions occupy an extra pointer's worth of space (8 bytes on 64-bit systems).
5. Practice: Geometric Shape Area Calculation
▶ Example 1: Using Polymorphism to Calculate Areas of Different Shapes (Difficulty ⭐⭐)
#include <iostream>
#include <vector>
#include <cmath>
// Abstract base class
class Shape {
public:
virtual double area() = 0;
virtual void printInfo() = 0;
virtual ~Shape() {}
};
// Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
void printInfo() override {
std::cout << "Circle, radius = " << radius
<< ", area = " << area() << std::endl;
}
};
// Rectangle
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
void printInfo() override {
std::cout << "Rectangle, " << width << " x " << height
<< ", area = " << area() << std::endl;
}
};
int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(4.0, 6.0));
for (Shape* s : shapes) {
s->printInfo();
}
// Free memory
for (Shape* s : shapes) {
delete s;
}
return 0;
}
Output:
Circle, radius =
Rectangle, x
❓ FAQ
Q: Why must the destructor be virtual if there are virtual functions? A: If you
deletea base class pointer pointing to a derived class object, and the base class destructor is not virtual, only the base class destructor will be called, causing resource leaks in the derived class.CPPAnimal* a = new Dog("Buddy"); delete a; // ❌ If ~Animal() is not virtual, Dog's destructor won't be calledGolden Rule: If a base class has virtual functions,
~BaseClass()must bevirtual.
Q: What's the difference between override and final? A: > -
override: tells the compiler this function overrides a base class virtual function (lets the compiler check for you) > -final: tells the compiler this function cannot be overridden (used in derived classes)CPPclass Dog : public Animal { public: void speak() override final { ... } // final: derived classes can no longer override speak() };
Q: When should I use polymorphism? A: When you need to handle different types of objects through a unified interface.
Typical scenarios:
- Games: all characters inherit from
Character, managed uniformly withCharacter*- GUI: all controls inherit from
Widget, drawn uniformly withWidget*- File systems: all files inherit from
File, read/written uniformly withFile*
▶ Example 3: Virtual Function Polymorphism (Difficulty ⭐)
#include <iostream>
#include <string>
class Shape {
public:
virtual double area() { return 0; }
virtual ~Shape() {}
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14 * radius * radius; }
};
int main() {
Shape* s = new Circle(2.0);
std::cout << "Surface Product: " << s->area() << std::endl;
delete s;
return 0;
}
Output:
Surface Product:
virtual to declare virtual functions and override to explicitly indicate overriding. A base class pointer calling a derived class method is polymorphism.
- Polymorphism lets base class pointers call the correct derived class implementation
- Use
virtualin the base class to declare virtual functions - Use
overridein the derived class (let the compiler check for you) - If a class has virtual functions, its destructor must also be
virtual - Pure virtual functions (
= 0) force derived classes to override - Classes with pure virtual functions are abstract classes and cannot be instantiated
📖 Summary
- Polymorphism: base class pointer calls derived class methods
- virtual: declares virtual functions
- override: explicitly indicates overriding a base class method
- Virtual destructor: ensures proper cleanup of derived class resources
📝 Exercises
-
Basic (Difficulty ⭐): Define a
Vehiclebase class with a pure virtual functionvoid move(). HaveCarandBicycleinherit from it, outputting "The car is driving on the road" and "The bicycle is riding on the bike lane" respectively. -
Intermediate (Difficulty ⭐⭐): Extend the above program by adding a
void stop()virtual function, withCaroutputting "The car pulls over" andBicycleoutputting "The bicycle brakes". -
Challenge (Difficulty ⭐⭐⭐): Design an employee salary system:
-
Employeeabstract base class (name, employee ID,virtual double calcSalary() = 0) -
FullTimeEmployee(monthly salary) -
PartTimeEmployee(hourly wage × hours worked) -
Use
vector<Employee*>to store all employees and uniformly callcalcSalary() -
Virtual functions: declared in base class, overridden in derived class, runtime polymorphism
- virtual keyword declares virtual functions
- override checks for correct overriding
- Pure virtual functions and abstract classes cannot be instantiated
- Virtual function table (vtable) implements runtime polymorphism
6. 🚀 Next Steps
Now that you've learned polymorphism, let's move on to Templates (Lesson 33) — making functions and classes support arbitrary types for true "generic programming"!