В чём разница между 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, ограничения и диагностируемые симптомы ошибки.