Перейти к содержимому
Теория · Классы и ООП · урок 8 из 13

virtual, override и полиморфизм

Через ссылку на базовый класс можно работать с любым производным классом. Но обычный метод, вызванный через такую ссылку, берётся из базового класса, даже если производный класс объявил свой:

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
}

Компилятор выбирает area по типу ссылки s, то есть Shape, а не по объекту, на который она ссылается.

Виртуальные функции

Метод, объявленный в базовом классе со словом virtual, становится виртуальной функцией (virtual function). Вызов такой функции через ссылку или указатель выбирает версию по настоящему типу объекта, и выбор делается во время работы программы. Это и есть полиморфизм (polymorphism): один и тот же вызов s.area() работает по-разному для разных фигур.

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

С таким Shape та же main выводит 4 4. Слово override просит компилятор проверить, что метод переопределяет (overrides) виртуальную функцию базового класса. Совпасть должны имя, типы параметров и const, иначе программа не скомпилируется.

Выбор по настоящему типу работает только через ссылку или указатель. Копия Shape, срезанная с объекта Square, — это обычный объект Shape, и вызов area у неё выполняет Shape::area.

Абстрактный класс

У «фигуры вообще» нет разумной площади. Тогда виртуальную функцию объявляют без тела, с = 0: virtual double area() const = 0;. Класс с такой функцией — абстрактный (abstract class): создать его объект нельзя, а ссылки и указатели на него можно. Производный класс, который переопределил все такие функции, уже не абстрактный.

Базовому классу с виртуальными функциями дают и виртуальный деструктор: virtual ~Shape() {}. Тогда объект производного класса, который уничтожают через указатель на базовый класс, уничтожается целиком.

dynamic_cast

Иногда по указателю на базовый класс нужно узнать, на какой объект он указывает. dynamic_cast<Square*>(p) проверяет это во время работы программы. Если p указывает на Square или объект производного от него класса, результат — указатель Square*, иначе — nullptr. Такая проверка требует, чтобы у Shape была хотя бы одна виртуальная функция, иначе код не скомпилируется.

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

Частая ошибка

В производном классе забыли 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]

Без const получилась другая функция: она не переопределяет Shape::area, а скрывает её. Программа собирается, но вызов через ссылку на Shape по-прежнему возвращает 0. С override эту ошибку нашёл бы компилятор, поэтому пишите double area() const override.