Polymorphism
Polymorphism (Greek for “many forms”) is the ability to call the correct, specific behavior for an object without the calling code needing to know its exact concrete type. In practice, this usually means calling a function through a base-class pointer or reference and having the derived class's version of that function run.
Two kinds of polymorphism
Kind | Resolved when? | Mechanism in C++ |
|---|---|---|
Compile-time (static) polymorphism | At compile time, based on argument types | Function overloading, operator overloading, templates |
Runtime (dynamic) polymorphism | At runtime, based on the object's actual type |
|
Compile-time polymorphism
Function overloading picks which function to call based on argument types the compiler already knows about — no runtime decision is needed.
Compile-time polymorphism via overloading
void print(int value) { std::cout << "int: " << value << "\n"; }
void print(double value) { std::cout << "double: " << value << "\n"; }
void print(const std::string& value) { std::cout << "string: " << value << "\n"; }
int main() {
print(42); // calls print(int) — decided at compile time
print(3.14); // calls print(double)
print("hello"); // calls print(const std::string&)
}Runtime polymorphism
Runtime polymorphism is what lets a single line of code — shape->area() — do different things depending on whether shape actually points to a Circle, a Rectangle, or any other derived type, decided while the program is running rather than baked in at compile time.
The goal: one call site, many behaviors
class Animal {
public:
virtual void speak() const { std::cout << "...\n"; }
};
class Dog : public Animal {
public:
void speak() const override { std::cout << "Woof!\n"; }
};
class Cat : public Animal {
public:
void speak() const override { std::cout << "Meow!\n"; }
};
void makeItSpeak(const Animal& a) {
a.speak(); // which version runs depends on the object's *actual* type
}
int main() {
Dog d;
Cat c;
makeItSpeak(d); // Woof!
makeItSpeak(c); // Meow!
}Why this needs the virtual keyword
Here is the part that surprises many people coming from languages like Java or C# (where every method is virtual by default): in C++, calling a method through a base-class pointer or reference uses static binding — the compile-time type — unless that method is explicitly marked virtual. Without virtual, makeItSpeak above would print ... for both a Dog and a Cat, because the compiler would resolve a.speak() to Animal::speak at compile time based on the parameter's declared type (const Animal&), completely ignoring what the object actually is at runtime.
What's next
See exactly how
virtualchanges what code gets called, including a worked with/without comparison, in Virtual Functions.Abstract Classes build on virtual functions to define pure interfaces that cannot be instantiated on their own.