C++ Runtime Polymorphism
In this page:
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
// 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;
}
Login to try C/C++/Java/PHP code in the editor
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
// 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;
}
Login to try C/C++/Java/PHP code in the editor
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
// 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;
}
Login to try C/C++/Java/PHP code in the editor
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
// 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;
}
Login to try C/C++/Java/PHP code in the editor
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
// 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;
}
Login to try C/C++/Java/PHP code in the editor
- Forgetting
virtual, so the base version is called through the pointer. - Assigning a derived object to a base object by value, which slices off the derived part and loses polymorphism.
- Forgetting a virtual destructor when deleting through a base pointer.
Chapter Quiz — Complete all 11 topics to unlock
0/11 topics done
Complete these topics first: