Skip to content
Theory · Classes and OOP · lesson 8 of 13

virtual, override and polymorphism

Through a reference to a base class, code can work with any derived class. But an ordinary method called through such a reference comes from the base class, even when the derived class declares its own:

C++
#include <iostream>

struct Shape {
    double area() const { return 0; }
};
struct Square : public Shape {
    double side = 2;
    double area() const { return side * side; }
};

int main() {
    Square sq;
    const Shape& s = sq;
    std::cout << sq.area() << ' ' << s.area() << '\n';  // 4 0
}

The compiler picks area by the type of the reference s, which is Shape, not by the object it refers to.

Virtual functions

A method declared in the base class with the word virtual becomes a virtual function. A call through a reference or a pointer picks its version by the object's real type, while the program runs. This is polymorphism: the same call s.area() works differently for different shapes.

C++
struct Shape {
    virtual double area() const { return 0; }
};
struct Square : public Shape {
    double side = 2;
    double area() const override { return side * side; }
};

With this Shape, the same main prints 4 4. The word override asks the compiler to check that the method overrides a virtual function of the base class. The name, the parameter types and const must all match, or the program does not compile.

Picking by the real type works only through a reference or a pointer. A Shape copy sliced from a Square object is an ordinary Shape object, and calling area on it runs Shape::area.

Abstract classes

A "shape in general" has no sensible area. Then the virtual function is declared without a body, with = 0: virtual double area() const = 0;. A class with such a function is abstract: no object of it can be created, but references and pointers to it can. A derived class that overrides all such functions is no longer abstract.

A base class with virtual functions also gets a virtual destructor: virtual ~Shape() {}. Then a derived object destroyed through a pointer to the base class is destroyed completely.

dynamic_cast

Sometimes code needs to find out what object a base-class pointer actually points to. dynamic_cast<Square*>(p) checks this at run time. If p points to a Square or an object derived from it, the result is a Square* pointer; otherwise, it is nullptr. This check needs Shape to have at least one virtual function; otherwise, the code does not compile.

C++
Square sq;
Shape* p = &sq;
Square* q = dynamic_cast<Square*>(p);
if (q != nullptr) {
    std::cout << q->side << '\n';  // 2
}

Common mistake

The derived class is missing const:

C++
struct Shape {
    virtual double area() const { return 0; }
};
struct Square : public Shape {
    double side = 2;
    double area() { return side * side; }  // ✗
};
TEXT
warning: 'Square::area' hides overloaded virtual function [-Woverloaded-virtual]

Without const, it is a different function: it does not override Shape::area but hides it. The program builds, but the call through a reference to Shape still returns 0. With override, the compiler would catch this mistake, so write double area() const override.