Skip to content

Intermediate · Operator overloading, inheritance and polymorphism · topic 19 of 42 · 30 min

Virtual functions and polymorphism

Explanation

A virtual function can be overridden in a derived class, and a call through a base pointer or reference runs the derived version, chosen at run time. This is polymorphism: one interface, many behaviours. Mark overrides with override, and give polymorphic base classes a virtual destructor.

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.

cpp
#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 499
The call m->pay runs the version of the real object, decided at run time. This is dynamic dispatch.

Static 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.

Think of it like this: A virtual function is like pressing "Play" on a universal remote. The remote (base reference) has one Play button, but the TV, the music system and the projector each do their own thing when it is pressed. The remote does not need to know which device it is pointing at.

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

cpp
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
};
override turns a silent mistake (a slightly different signature that creates a NEW function) into a compile error. Always write it.

Virtual destructors

Careful: If you delete a derived object through a base pointer and the base destructor is not virtual, the behaviour is undefined: usually only the base part is destroyed and the derived members leak. Rule: any class with virtual functions, meant to be used through base pointers, needs a public virtual destructor (virtual ~Base() = default;).

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

cpp
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 100
Pass polymorphic objects by reference or (smart) pointer.
Tip: Not everything needs to be virtual. Use virtual only where you really expect different behaviour per derived class; plain functions are simpler and slightly faster.

Need 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

Virtual functions

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.

cpp
#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");
}
= 0 makes send pure virtual; the next topic explains it.

Practice (0/4 done)

Task 1 · Question

What does the override keyword do?

Task 2 · Question

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(); }

Task 3 · Question

Base* p = new Derived; delete p; Base's destructor is NOT virtual. What happens?

Task 4 · Practice

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

Question 1

1.When is a virtual function call resolved?

Question 2

2.What is a vtable?

Question 3

3.What does final on a class mean?

Question 4

4.Which call is polymorphic?

Question 5

5.A base constructor calls a virtual function. Which version runs?

Read more (free)

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.

From Overriding and polymorphism

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.

From Abstract classes and polymorphism

What does base.Describe() do inside an override?Related

Answer: Calls the base class version of Describe

base lets you reuse the original implementation.

From Inheritance: base and derived classes

Which base members can a derived class use directly?Related

Answer: public and protected

private members stay hidden even from derived classes.

From Inheritance: building on an existing class

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. 1 more topic in “Operator overloading, inheritance and polymorphism” before its exam: Abstract classes, interfaces and good class design.
  2. Module 5 exam: Operators, inheritance and polymorphism · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, The Standard Template Library (STL). 42 topics left in the course before the final exam.

← Previous: Inheritance: building on an existing class