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

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, а не по объекту, на который она ссылается.

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

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

Метод, объявленный в базовом классе со словом virtual, становится виртуальной функцией. При вызове такой функции через ссылку или указатель программа во время работы выбирает версию по настоящему типу объекта.

Производный класс переопределяет виртуальную функцию, когда объявляет метод с тем же именем, теми же типами параметров и тем же const. Тогда вызов через ссылку на базовый класс выполняет его версию.

ВживуюКакую area вызовет программаs.area() → Shape::area
s.area() выводит: 0
sizeof(Shape) = 1, sizeof(Square) = 8 на x86-64; в задачах курса (wasm32) — 1 и 8

Метод не виртуальный: компилятор берёт area по типу s — const Shape&, а не по объекту sq. Выводит 0.

Включите virtual и сравните s.area() и copy.area()

Переключайте virtual и способ вызова и следите, чью area выполняет программа. Обратите внимание, откуда она берёт версию: из типа ссылки или из таблицы самого объекта.

Это и есть полиморфизм: один и тот же вызов 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 просит компилятор проверить, что метод действительно переопределяет виртуальную функцию базового класса. Если имя, типы параметров или const не совпали, программа не скомпилируется.

Копия не выбирает версию

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

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

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

Главное

  • Обычный метод выбирается по типу ссылки, виртуальный — по настоящему типу объекта.
  • Переопределение требует того же имени, тех же типов параметров и того же const.
  • override просит компилятор проверить, что метод действительно переопределяет функцию базового класса.
  • У срезанной копии вызывается версия базового класса.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом