C++ by Example
C++ by Example

Polymorphism

2 min read

Polymorphism

Polymorphism lets you call derived-class methods through a base-class pointer or reference, with the right function selected at runtime. This makes it possible to write code that works with any subtype without knowing the concrete type.

Virtual functions

Without virtual, function calls are resolved at compile time based on the pointer's declared type (static dispatch). With virtual, they are resolved at runtime based on the object's actual type (dynamic dispatch):

#include <iostream>

class Shape {
public:
    virtual double area() const {
        return 0.0;
    }

    virtual ~Shape() = default;   // always virtual in polymorphic base classes
};

class Circle : public Shape {
    double radius;
public:
    explicit Circle(double r) : radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
};

class Square : public Shape {
    double side;
public:
    explicit Square(double s) : side(s) {}
    double area() const override { return side * side; }
};

int main() {
    Circle c(5.0);
    Square s(4.0);

    Shape* shapes[] = {&c, &s};
    for (Shape* sh : shapes) {
        std::cout << sh->area() << "\n";   // correct type called each time
    }
}

Output:

78.5397
16

override and final

override tells the compiler that this function must override a base virtual function. If the signatures do not match, it is a compile error — catching typos immediately:

class Triangle : public Shape {
    double base, height;
public:
    Triangle(double b, double h) : base(b), height(h) {}
    double area() const override { return 0.5 * base * height; }
};

final prevents a method from being overridden further, or prevents a class from being subclassed:

class Singleton final { /* cannot be inherited */ };

class OptimizedCircle : public Circle {
    double area() const override final { /* cannot be overridden again */ }
};

Pure virtual functions and abstract classes

A pure virtual function has no implementation in the base class — it is declared with = 0. Any class with at least one pure virtual function is abstract and cannot be instantiated:

class Drawable {
public:
    virtual void draw() const = 0;   // pure virtual
    virtual ~Drawable() = default;
};

class Sprite : public Drawable {
public:
    void draw() const override { std::cout << "drawing sprite\n"; }
};

// Drawable d;   // compile error: abstract class
Sprite s;        // OK: all pure virtuals implemented

The virtual destructor rule

If a class has any virtual function, its destructor must be virtual. Otherwise, deleting a derived object through a base pointer causes undefined behavior:

Shape* p = new Circle(3.0);
delete p;   // calls Circle::~Circle, then Shape::~Shape — correct because ~Shape is virtual

Without a virtual destructor, delete p would only call Shape::~Shape, leaking Circle's resources.

How virtual dispatch works

The compiler adds a hidden pointer — the vptr — to each object of a class with virtual functions. The vptr points to a vtable: a table of function pointers for that class. Each virtual call dereferences the vptr, looks up the function pointer, and calls through it. This is one indirect function call — typically negligible overhead.

Interfaces via abstract classes

An abstract class with only pure virtual functions acts as an interface:

class Serializable {
public:
    virtual std::string serialize() const = 0;
    virtual void deserialize(const std::string&) = 0;
    virtual ~Serializable() = default;
};

Any class implementing all pure virtuals can be used wherever Serializable* or Serializable& is expected.

C++ by Example
C++ by Example

Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.

View book →