ООП в C++ — мощь и ответственность
Многие языки скрывают детали объектной модели. C++ — нет. Здесь вы решаете: использовать виртуальные функции или шаблоны, управлять памятью вручную или через умные указатели, строить иерархии или предпочесть композицию. Это даёт огромную гибкость, но и ставит ловушки: срезка объектов, забытый виртуальный деструктор, алмаз наследования — классические источники багов, которые компилятор не всегда поймает.
C++ поддерживает четыре принципа ООП: инкапсуляцию, наследование, полиморфизм и абстракцию. Но в отличие от Java или Python, механизм реализации напрямую влияет на производительность, размер объекта и ABI.
Карта темы
- Классы и контроль доступа — синтаксис класса,
public/protected/private,friend,this,static. - Система типов — что такое объект, размер, aggregate vs class.
- Инкапсуляция — защита инварианта, а не просто «приватные поля».
- Наследование — иерархия типов, layout, EBO.
- Полиморфизм — динамический dispatch и срезка объектов.
- Виртуальные функции — vtable,
override,final, ковариантные возвраты. - Чисто виртуальные функции — абстрактные классы и NVI.
- Множественное наследование — алмаз, виртуальная база, число
vptr. - Конструкторы — список инициализации, делегирование,
explicit. - Инициализация полей — порядок объявления, default member init, aggregate.
- Семантика копирования — глубокая vs поверхностная, copy-and-swap.
- Специальные методы — Rule of Five и Rule of Zero,
= default/= delete. - Жизненный цикл объекта — порядок ctor/dtor, этапы
vptr, виртуальный деструктор.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Нет virtual у деструктора базового класса | UB при delete base_ptr → утечка ресурсов |
Срезка объекта (Animal a = Dog(...)) | Dog-часть обрезается, vptr перезаписывается |
Забыли override | Молчаливое скрытие базового метода, а не переопределение |
| Виртуальный вызов в конструкторе/деструкторе | Вызывается метод текущего (частично построенного) класса |
Алмазное наследование без virtual | Два экземпляра базы, неоднозначность при обращении к членам |
| Rule of Five наполовину | Неправильно сгенерированные copy/move операции → двойное освобождение |
| Поверхностное копирование указателей-владельцев | Двойное освобождение, повисшие указатели |
| Порядок init-list ≠ порядок объявления полей | Молча используется объявление, зависимости между полями ломаются |
Конструктор с одним параметром без explicit | Скрытые неявные преобразования в самых неожиданных местах |
| Удаление через указатель на базу при невиртуальном деструкторе | Деструктор производного класса не вызывается → утечка |
Значение для собеседований
ООП в C++ — одна из самых частых тем на интервью уровня middle и выше. Причина: здесь легко проверить, понимает ли кандидат механику, а не просто знает термины.
Что проверяет интервьювер:
- Понимание vtable и vptr, а не просто «виртуальные функции вызывают нужный метод»
- Знание объектного слайсинга и почему полиморфизм работает только через указатели/ссылки
- Осознание стоимости виртуального dispatch (косвенный вызов, запрет инлайнинга, кэш-промахи)
- Rule of Five: почему пяти методов именно пять, и что происходит, если определить только деструктор
- Алмазная проблема и виртуальное наследование
- Поведение виртуальных вызовов в конструкторах и деструкторах
- Порядок инициализации полей и почему list-order != declaration-order — UB
explicitи неявные преобразования
Типичные вопросы:
- Как работает vtable? Сколько vptr у объекта с множественным наследованием?
- Что такое object slicing и как от него защититься?
- Почему деструктор базового класса должен быть виртуальным?
- Что делает
override? Чем оно отличается от простоvirtual? - Объясните алмазную проблему и как её решает виртуальное наследование.
- Rule of Zero vs Rule of Five — когда применять каждый?
- В каком порядке инициализируются поля и базовые классы?
Типичная ошибка: отвечать «ООП — это классы, наследование и полиморфизм» без объяснения механики. Интервьювер ищет понимание dispatch, layout и ownership, а не пересказ учебника.