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

C++ Runtime Polymorphism

Runtime polymorphism means the program picks which version of a function to run while it is running, based on the real object. It is like a remote control that does the right thing for each device.
Syntax
cpp
Base *pointer_name = new Derived();
pointer_name->virtual_method();  // calls Derived's version at runtime
delete pointer_name;

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 std::cout and std::cin
#include <iostream>

// Define the type Animal
class Animal {
// Members below can be accessed from outside the class
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

// Define the type Dog
class Dog : public Animal {
// Members below can be accessed from outside the class
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

// Program execution starts in main()
int main() {
	// Allocate an object on the heap with new
	Animal *a = new Dog();
	a->speak();
	// Free the heap memory with delete
	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 std::cout and std::cin
#include <iostream>

// Define the type Shape
class Shape {
// Members below can be accessed from outside the class
public:
	virtual void draw() { std::cout << "Shape" << std::endl; }
};

// Define the type Circle
class Circle : public Shape {
// Members below can be accessed from outside the class
public:
	void draw() override { std::cout << "Circle" << std::endl; }
};

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

// Program execution starts in main()
int main() {
	// Allocate an object on the heap with new
	Shape *shapes[2] = {new Circle(), new Square()};
	// Loop: repeat while the condition holds
	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 std::cout and std::cin
#include <iostream>

// Define the type Animal
class Animal {
// Members below can be accessed from outside the class
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

// Define the type Dog
class Dog : public Animal {
// Members below can be accessed from outside the class
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

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

// Program execution starts in main()
int main() {
	Dog d;
	makeSpeak(d);
	// Return 0 to signal that the program finished successfully
	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 std::cout and std::cin
#include <iostream>

// Define the type Animal
class Animal {
// Members below can be accessed from outside the class
public:
	virtual void speak() { std::cout << "Animal" << std::endl; }
};

// Define the type Dog
class Dog : public Animal {
// Members below can be accessed from outside the class
public:
	void speak() override { std::cout << "Dog" << std::endl; }
};

// Program execution starts in main()
int main() {
	// Allocate an object on the heap with new
	Animal *a = new Dog();
	a->speak();
	// Free the heap memory with delete
	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 std::cout and std::cin
#include <iostream>

// Define the type PaymentMethod
class PaymentMethod {
// Members below can be accessed from outside the class
public:
	virtual void pay() { std::cout << "Generic payment" << std::endl; }
};

// Define the type CreditCard
class CreditCard : public PaymentMethod {
// Members below can be accessed from outside the class
public:
	void pay() override { std::cout << "Paid with credit card" << std::endl; }
};

// Program execution starts in main()
int main() {
	// Allocate an object on the heap with new
	PaymentMethod *method = new CreditCard();
	method->pay();
	// Free the heap memory with delete
	delete method;
	return 0;
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting virtual, so the base version is called through the pointer.
  2. Assigning a derived object to a base object by value, which slices off the derived part and loses polymorphism.
  3. Forgetting a virtual destructor when deleting through a base pointer.

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.