C++JuniorTechnical

В чём разница между override и virtual в C++?

virtual объявляет функцию полиморфной в базовом классе; override в производном классе подтверждает переопределение и вызывает ошибку компиляции при несовпадении сигнатур, предотвращая скрытые баги.

virtual и override в C++

virtual объявляет функцию участницей механизма динамического полиморфизма: при вызове через указатель или ссылку на базовый класс будет вызвана версия производного класса, определённая в vtable. override — спецификатор C++11, который явно говорит компилятору «эта функция переопределяет виртуальную функцию базового класса» и добавляет проверку на этапе компиляции.

virtual

  • Помечается в базовом классе.
  • Наследуется автоматически: функция остаётся виртуальной во всех производных классах, даже без повторного слова virtual.
  • Вызов идёт через vtable (pointer indirection), поэтому чуть дороже невиртуального вызова.

override

  • Указывается в производном классе после сигнатуры функции.
  • Если сигнатура не совпадает с базовой (опечатка в имени, другие параметры, другая const-квалификация) — ошибка компиляции. Без override такая функция тихо создаёт новую невиртуальную функцию, скрывая базовую.
  • Является документацией намерения и защитой от регрессий при рефакторинге.
#include <iostream>
#include <memory>

struct Animal {
    virtual void speak() const {
        std::cout << "...\n";
    }
    virtual void move() const {
        std::cout << "moves\n";
    }
    virtual ~Animal() = default;  // виртуальный деструктор обязателен!
};

struct Dog : Animal {
    void speak() const override {      // OK — переопределяет Animal::speak
        std::cout << "Woof\n";
    }
    // void spek() const override;    // ошибка компиляции — нет такой функции в базе
    // void move() const;             // без override: скрывает, не переопределяет
};

struct Cat : Animal {
    void speak() const override {
        std::cout << "Meow\n";
    }
};

int main() {
    std::unique_ptr<Animal> animals[] = {
        std::make_unique<Dog>(),
        std::make_unique<Cat>(),
    };
    for (auto& a : animals) {
        a->speak();   // Dog: Woof, Cat: Meow — динамический полиморфизм
    }
}

final

Ещё один спецификатор C++11. Запрещает дальнейшее переопределение функции или наследование от класса:

struct Base {
    virtual void foo() {}
};

struct Mid : Base {
    void foo() override final {}  // нельзя переопределить в Derived
};

struct Derived : Mid {
    // void foo() override {}  // ошибка компиляции
};

struct Sealed final : Base {   // нельзя наследоваться от Sealed
    void foo() override {}
};

Виртуальный деструктор

Если класс предназначен для наследования и объект будет удаляться через указатель на базовый класс, деструктор базового класса должен быть virtual. Иначе деструктор производного класса не вызывается — утечка ресурсов и UB.

Подводные камни

  • Функция без override, сигнатура которой отличается от базовой (например, const пропущен), создаёт новую функцию вместо переопределения — компилятор не предупредит без override.
  • Виртуальный вызов в конструкторе или деструкторе не полиморфный: вызывается версия того класса, чей конструктор/деструктор выполняется в данный момент.
  • Невиртуальный деструктор в полиморфном базовом классе приводит к неполному разрушению объекта.
  • Vtable добавляет один указатель (8 байт) к каждому объекту класса с виртуальными функциями — важно при миллионах мелких объектов.
  • Виртуальные функции плохо инлайнятся: компилятор не всегда может девиртуализировать вызов, что снижает производительность в горячих циклах.
  • Слово virtual в производном классе без override избыточно, но не ошибка — путает читателя кода.

Common mistakes

  • Объяснять override и virtual только по синтаксису, без жизненного цикла и стоимости.
  • Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
  • Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
  • Показывать сырой указатель без объяснения владельца и момента освобождения.

What the interviewer is testing

  • Кандидат формулирует точную модель для override и virtual, а не только определение.
  • Пример компилируем, безопасен по lifetime и соответствует версии технологии.
  • Названы trade-off, ограничения и диагностируемые симптомы ошибки.

Sources

Related topics