← Back to C++ Course | Chapter 10: Polymorphism & Abstraction | Lesson 6 of 11

C++ Runtime Polymorphism

Runtime Polymorphism Explained

Runtime polymorphism lets a C++ program decide, while it's actually running, which specific function implementation to execute — a behavior known as late binding or dynamic binding, in contrast to decisions the compiler can make ahead of time.

Example: Runtime Polymorphism Explained

cpp
#include <iostream>

class Animal {
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

class Dog : public Animal {
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

int main() {
	Animal *a = new Dog();
	a->speak();
	delete a;
	return 0;
}

Using Base Class Pointers

Using base class pointers to refer to objects of various derived types lets you handle many different concrete components through a single, unified interface, without the calling code needing to know or care exactly which derived type it's dealing with.

Example: Using Base Class Pointers

cpp
#include <iostream>

class Shape {
public:
	virtual void draw() { std::cout << "Shape" << std::endl; }
};

class Circle : public Shape {
public:
	void draw() override { std::cout << "Circle" << std::endl; }
};

class Square : public Shape {
public:
	void draw() override { std::cout << "Square" << std::endl; }
};

int main() {
	Shape *shapes[2] = {new Circle(), new Square()};
	for (int i = 0; i < 2; i++) shapes[i]->draw();
	return 0;
}

Using Base References

Passing derived objects to functions that accept a parent-class reference parameter is an especially clean way to write polymorphic code, since it avoids the null-checking and explicit pointer syntax that base-pointer-based polymorphism requires.

Example: Using Base References

cpp
#include <iostream>

class Animal {
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

class Dog : public Animal {
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

void makeSpeak(Animal &a) {
	a.speak();
}

int main() {
	Dog d;
	makeSpeak(d);
	return 0;
}

Runtime vs Compile-time Performance

Dynamic binding does introduce a small performance cost from the VTABLE lookup needed to resolve which function to call at runtime, but in exchange it buys substantial structural flexibility — new derived types can be added later without changing any existing polymorphic code.

Example: Runtime vs Compile-time Performance

cpp
#include <iostream>

class Animal {
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

class Dog : public Animal {
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

int main() {
	Animal *a = new Dog();
	a->speak();
	delete a;
	return 0;
}

Real-world Polymorphic Design

Real-world software relies heavily on runtime polymorphism to stay extensible over time — payment processors that support new payment methods, or messaging systems that support new transport protocols, are both commonly built around a polymorphic base type.

Example: Real-world Polymorphic Design

cpp
#include <iostream>

class PaymentMethod {
public:
	virtual void pay() { std::cout << "Generic payment" << std::endl; }
};

class CreditCard : public PaymentMethod {
public:
	void pay() override { std::cout << "Paid with credit card" << std::endl; }
};

int main() {
	PaymentMethod *method = new CreditCard();
	method->pay();
	delete method;
	return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.