devnotes.

Polymorphism and Virtual Functions

Runtime polymorphism lets code call a method through a base-class pointer or reference and have the derived class's version actually run — the mechanism behind "one interface, many implementations."

class Shape {
public:
    virtual double area() const { return 0; }   // virtual enables overriding
    virtual ~Shape() {}                          // virtual destructor — see below
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override {               // 'override' confirms this matches a virtual
        return 3.14159 * radius * radius;
    }
};

void printArea(const Shape &s) {   // works for ANY Shape-derived type
    std::cout << s.area() << "\n";
}

int main() {
    Circle c(5);
    printArea(c);   // calls Circle's area(), not Shape's — resolved at RUNTIME
    return 0;
}

Without the virtual keyword, calling .area() through a Shape& would always run Shape's version, no matter what derived type the object actually is — this is called "static binding," decided at compile time based purely on the reference/pointer's declared type. Marking area() virtual switches to dynamic binding: the actual call is resolved at runtime based on the object's real type, via an internal mechanism called a vtable (virtual function table) that each polymorphic class carries.

override (C++11) isn't strictly required, but should always be used when overriding a virtual function — it makes the compiler verify the function genuinely matches a base class's virtual signature, catching typos (a mismatched parameter type, a missing const) that would otherwise silently create an unrelated new function instead of an override, a notoriously hard bug to spot without this keyword.

A virtual destructor in the base class is essential whenever a derived object might be deleted through a base-class pointer (Shape *s = new Circle(5); delete s;) — without it, only Shape's destructor runs, Circle's never does, and any resources Circle owns leak. A pure virtual function (virtual double area() const = 0;) has no implementation in the base class at all, forcing every derived class to provide one and making the base class abstract — it can no longer be instantiated directly, only used as an interface.

Polymorphism and Virtual Functions in C++ | DevNotes