Intermediate · Operator overloading, inheritance and polymorphism · topic 19 of 42 · 30 min
Virtual functions and polymorphism
Explanation
The problem
Suppose you keep a list of different payment methods: UPI, card and cash on delivery. The checkout code wants to call pay() on each one without caring which kind it is. With plain inheritance, calling pay() through an Account& or Payment& always runs the BASE version, because the compiler picks the function from the declared type. Virtual functions fix this.
#include <iostream>
#include <memory>
#include <vector>
class Payment {
public:
virtual ~Payment() = default; // virtual destructor
virtual void pay(long long paise) const {
std::cout << "Generic payment of " << paise / 100 << '\n';
}
};
class Upi : public Payment {
public:
void pay(long long paise) const override {
std::cout << "UPI collect request for Rs " << paise / 100 << '\n';
}
};
class Card : public Payment {
public:
void pay(long long paise) const override {
std::cout << "Card charged Rs " << paise / 100 << '\n';
}
};
int main() {
std::vector<std::unique_ptr<Payment>> methods;
methods.push_back(std::make_unique<Upi>());
methods.push_back(std::make_unique<Card>());
for (const auto& m : methods) m->pay(49900);
}
// UPI collect request for Rs 499
// Card charged Rs 499Static versus dynamic dispatch
For a normal function, the compiler decides which function to call from the type written in the code: static dispatch. For a virtual function called through a pointer or reference, the program looks at the actual object at run time: dynamic dispatch. This lets new payment types be added later without changing the checkout loop.
How it works: the vtable
Most compilers implement virtual functions with a vtable: a hidden table of function pointers for each class that has virtual functions. Each object of such a class carries a hidden pointer (the vptr) to its class's table. A virtual call follows the vptr to the table and calls the right entry. The cost is one pointer per object and an extra indirect call, which is small but stops the compiler from inlining.
override and final
class Card : public Payment {
public:
void pay(long long paise) override; // checked: must override a base virtual
// void pay(int paise) override; // error: does not override anything (wrong type)
};
class RuPayCard final : public Card { // no class may derive from RuPayCard
public:
void pay(long long paise) override final; // no further overrides allowed
};Virtual destructors
Calling virtual functions in constructors
Inside a base class constructor or destructor, virtual calls do NOT go to the derived version, because the derived part is not built yet (or is already destroyed). Avoid calling virtual functions there.
Polymorphism needs pointers or references
void checkout(const Payment& p) { p.pay(10000); } // polymorphic: OK
void checkoutCopy(Payment p) { p.pay(10000); } // sliced copy: always the base version
Upi u;
checkout(u); // UPI collect request for Rs 100
checkoutCopy(u); // Generic payment of 100Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Virtual functions and polymorphism · 1/6
Polymorphism means one interface with many behaviours. In C plus plus it comes from virtual functions.
Prefer a video by a teacher? Find free videos on “c++ virtual functions polymorphism vtable override virtual destructor” on YouTube ↗
In real life: Notification channels in an app
An app sends alerts by SMS, email and push notification. A Notifier base class with a virtual send() lets the "order shipped" code loop over the user's chosen channels and call send() on each. Adding WhatsApp later means writing one new derived class; the order code does not change.
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Notifier {
virtual ~Notifier() = default;
virtual void send(const std::string& msg) const = 0;
};
struct Sms : Notifier {
void send(const std::string& msg) const override { std::cout << "SMS: " << msg << '\n'; }
};
struct Email : Notifier {
void send(const std::string& msg) const override { std::cout << "Email: " << msg << '\n'; }
};
int main() {
std::vector<std::unique_ptr<Notifier>> channels;
channels.push_back(std::make_unique<Sms>());
channels.push_back(std::make_unique<Email>());
for (const auto& c : channels) c->send("Your order has shipped");
}Practice (0/4 done)
What does the override keyword do?
What prints? struct A { virtual void f() { std::cout << "A"; } }; struct B : A { void f() override { std::cout << "B"; } }; int main() { B b; A& r = b; A a = b; r.f(); a.f(); }
Base* p = new Derived; delete p; Base's destructor is NOT virtual. What happens?
Model shapes: a base class Shape with a virtual double area() const and a virtual std::string name() const, and derived classes Circle, Rectangle and Triangle that override both. Store them in std::vector<std::unique_ptr<Shape>> and print each name and area, then the total area.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Abstract classes, interfaces and good class designOperator overloading, inheritance and polymorphism · same module
- Operator overloadingOperator overloading, inheritance and polymorphism · same module
- Overriding and polymorphismJava Programming (Core to Spring Boot) · Inheritance, polymorphism and interfaces
- Abstract classes and polymorphismC# and .NET · Inheritance, interfaces and records
- Inheritance: base and derived classesC# and .NET · Inheritance, interfaces and records
- Trait objects and dynamic dispatchRust Programming · Modules, crates, traits and generics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
When is a virtual function call resolved?
Answer: At run time, from the actual object type, when called through a pointer or reference
Dynamic dispatch picks the function using the real object's type.
What is a vtable?
Answer: A per-class table of function pointers used for virtual calls
Objects of classes with virtual functions hold a hidden pointer to their class's vtable.
What does final on a class mean?
Answer: No class can derive from it
final stops further inheritance (or further overriding, when used on a function).
Which call is polymorphic?
Answer: void f(const Shape& s) { s.area(); }
Calls through references (or pointers) use dynamic dispatch.
A base constructor calls a virtual function. Which version runs?
Answer: The base version, because the derived part is not built yet
During base construction the object behaves as a base object.
Are static methods polymorphic?Related
Answer: No, they are hidden, not overridden
Static methods are chosen by the static type.
What is polymorphism?Related
Answer: One piece of code working with many types, each responding in its own way
Calls through a base type run each object's own override.
What does base.Describe() do inside an override?Related
Answer: Calls the base class version of Describe
base lets you reuse the original implementation.
Which base members can a derived class use directly?Related
Answer: public and protected
private members stay hidden even from derived classes.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Operator overloading, inheritance and polymorphism” before its exam: Abstract classes, interfaces and good class design.
- Module 5 exam: Operators, inheritance and polymorphism · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, The Standard Template Library (STL). 42 topics left in the course before the final exam.