ООП
Виртуальная диспетчеризация, vtable, срезка объектов, правило пяти и полиморфный дизайн.
60 вопросов
JuniorТеорияОчень частоЧто такое абстрактный класс и зачем он нужен?
Что такое абстрактный класс и зачем он нужен?
Абстрактный класс имеет хотя бы одну чисто виртуальную функцию (= 0); его нельзя инстанциировать напрямую, он служит контрактом для производных. В C++ pure-virtual функция может иметь тело — это полезно для деструктора.
Типичные ошибки
- ✗Попытка инстанциировать абстрактный класс напрямую — ошибка компилятора
- ✗Не предоставлять виртуальный деструктор в абстрактном базовом классе — приводит к частичному разрушению через указатель на базу
- ✗Путать абстрактный класс с интерфейсом: в C++ оба выражаются одинаково; 'интерфейс' — класс только с чисто виртуальными функциями и без данных
Уточняющие вопросы
- →Может ли чисто виртуальная функция иметь реализацию? Когда это полезно?
- →В чём разница между абстрактным классом и интерфейсом в других языках?
JuniorТеорияОчень частоРазница между private / protected / public и где они используются?
Разница между private / protected / public и где они используются?
public — доступно везде; protected — внутри класса и его производных; private — только внутри самого класса и его друзей. По умолчанию в class — private, в struct — public.
Типичные ошибки
- ✗Думать, что
protectedозначает 'доступно снаружи иерархии' — нет; только производные классы его видят - ✗Забывать, что
friendобходит все спецификаторы доступа для конкретного класса/функции - ✗Использовать
protectedполя данных в базовых классах — предпочтительнееprivate+ protected-геттеры для сохранения инкапсуляции
Уточняющие вопросы
- →Как спецификатор при наследовании (
class D : private Base) изменяет видимость членов Base в D? - →Почему
privateполя данных предпочтительнееprotectedв базовых классах?
JuniorТеорияОчень частоКакая особенность константных методов-членов класса? Какие методы можно вызвать из константных объектов?
Какая особенность константных методов-членов класса? Какие методы можно вызвать из константных объектов?
Константный метод объявляется с const после списка параметров; внутри this — указатель на const, функция не может менять не-mutable поля. На константном объекте вызываются только const-методы.
Типичные ошибки
- ✗Вызывать неконстантный метод на константном объекте — ошибка компиляции
- ✗Возвращать неконстантную ссылку/указатель на внутренний член из const-метода — нарушение логической константности
- ✗Не знать, что
mutableпозволяет изменять поле даже в const-методе (например, для кэшированного значения)
Уточняющие вопросы
- →В чём разница между побитовой константностью и логической константностью?
- →Как избежать дублирования кода при предоставлении как const, так и не-const перегрузок?
JuniorТеорияОчень частоЧто такое конструктор? Какие типы конструкторов вы знаете?
Что такое конструктор? Какие типы конструкторов вы знаете?
Конструктор инициализирует новый объект. Виды: по умолчанию, копирования (const T&), перемещения (T&&), преобразующий (один не-explicit аргумент), делегирующий, наследуемый (using Base::Base).
Типичные ошибки
- ✗Думать, что конструктор по умолчанию генерируется всегда — он подавляется, если объявлен любой другой конструктор
- ✗Путать преобразующий конструктор с явным преобразованием —
explicitпредотвращает неявные преобразования - ✗Не знать, что конструкторы не имеют возвращаемого типа и не могут быть виртуальными
Уточняющие вопросы
- →Когда компилятор генерирует конструктор перемещения, а когда подавляет?
- →В чём разница между делегирующим конструктором и вызовом конструктора базового класса в списке инициализации?
JuniorТеорияОчень частоЧто такое инкапсуляция? Как она реализуется в C++?
Что такое инкапсуляция? Как она реализуется в C++?
Инкапсуляция объединяет данные и операции и скрывает детали реализации; в C++ это спецификаторы доступа: private данные открываются только через public/protected методы.
Типичные ошибки
- ✗Делать все данные
publicи называть это 'инкапсуляцией' — данные должны быть private с контролируемым доступом - ✗Предоставлять сырые геттеры и сеттеры для каждого поля — лучше, чем public-данные, но всё равно раскрывает реализацию
- ✗Путать инкапсуляцию со скрытием информации — инкапсуляция — механизм; скрытие информации — принцип
Уточняющие вопросы
- →В чём разница между инкапсуляцией и абстракцией?
- →Когда вы бы открыли ссылку на внутренний контейнер (например,
begin()/end())?
JuniorТеорияОчень частоДля чего используется наследование? Какие бывают типы наследования?
Для чего используется наследование? Какие бывают типы наследования?
Наследование моделирует «является» и переиспользует поведение базового класса: public, protected, private. Множественное объединяет несколько базовых классов; виртуальное избегает дублирования общих предков.
Типичные ошибки
- ✗Злоупотреблять наследованием там, где уместнее была бы композиция (является vs содержит)
- ✗Не знать разницу между public и private наследованием — private наследование означает 'реализован через', а не 'является'
- ✗Глубокие иерархии наследования (более 2-3 уровней) трудно поддерживать
Уточняющие вопросы
- →Что такое принцип подстановки Лисков и как он ограничивает дизайн наследования?
- →Когда композиция явно предпочтительнее наследования?
JuniorТеорияОчень частоЧто такое полиморфизм?
Что такое полиморфизм?
Полиморфизм — единый интерфейс для разных базовых типов. В C++ три вида: подтиповой (runtime) через виртуальные функции, параметрический (compile-time) через шаблоны и ad-hoc через перегрузку. Runtime-полиморфизм требует указателя или ссылки на базу.
Типичные ошибки
- ✗Ожидать виртуальной диспетчеризации для объекта по значению — срезка объектов удаляет производную часть
- ✗Путать ad-hoc перегрузку с полиморфизмом подтипов
- ✗Не знать, что шаблоны обеспечивают параметрический полиморфизм без накладных расходов времени выполнения
Уточняющие вопросы
- →Какова стоимость полиморфизма времени выполнения (поиск в vtable, промахи кэша)?
- →Когда выбрать статический полиморфизм (CRTP/шаблоны) вместо динамического (виртуальные функции)?
JuniorТеорияОчень частоЧто такое Rule of Three и как он был расширен до Rule of Five?
Что такое Rule of Three и как он был расширен до Rule of Five?
Rule of Three: если пишете один из (деструктор, copy-ctor, copy-assign) — почти наверняка нужны все три. C++11 добавил move-ctor/assign → Rule of Five. Rule of Zero: используйте RAII-члены, тогда спецчлены не нужны.
Типичные ошибки
- ✗Определить только деструктор и унаследовать кривой compiler-generated copy
- ✗Copy-ctor копирует указатели поверхностно — double delete
- ✗Смешивать Rule of Three (без move) с C++11 типами, ожидающими move
Уточняющие вопросы
- →Что компилятор делает по умолчанию для copy-ctor?
- →Почему Rule of Zero часто проще поддерживать?
JuniorТеорияОчень частоЗачем базовому классу нужен виртуальный деструктор и что будет при его отсутствии?
Зачем базовому классу нужен виртуальный деструктор и что будет при его отсутствии?
Удаление производного объекта через указатель на базовый класс с невиртуальным деструктором вызывает только деструктор базового класса — UB. Виртуальный деструктор гарантирует вызов производного деструктора, затем базовых. Полиморфные базовые классы обязаны его иметь.
Типичные ошибки
- ✗Не делать деструктор виртуальным в классе, предназначенном для наследования — распространённая причина утечек ресурсов
- ✗Добавлять виртуальный деструктор во все классы 'на всякий случай' — добавляет накладные расходы vtable; нужен только для полиморфных базовых классов
- ✗Не знать, что
= defaultили= 0можно использовать с виртуальным деструктором
Уточняющие вопросы
- →Каково правило: если есть хоть одна виртуальная функция, объявите виртуальный деструктор?
- →Допустим ли чисто виртуальный деструктор? Что нужно дополнительно предоставить?
JuniorТеорияОчень частоЧто делает ключевое слово virtual? Что такое виртуальные функции и зачем они нужны?
Что делает ключевое слово virtual? Что такое виртуальные функции и зачем они нужны?
virtual включает динамическую диспетчеризацию: нужное переопределение выбирается в рантайме по фактическому типу объекта, а не статическому типу указателя/ссылки. virtual нужно только в базовом объявлении; производные наследуют виртуальность.
Типичные ошибки
- ✗Писать
virtualна каждом переопределении в производных классах — безвредно, но избыточно; используйтеoverride - ✗Вызывать виртуальную функцию для объекта по значению — нет динамической диспетчеризации; используется объявленный тип
- ✗Забывать, что невиртуальные функции нельзя корректно переопределить — их можно только скрыть
Уточняющие вопросы
- →Какова стоимость производительности виртуального вызова по сравнению с прямым?
- →Можно ли объединить static и virtual на одной функции? Почему нет?
MiddleТеорияОчень частоВ чём разница между конструктором копирования и оператором присваивания копии?
В чём разница между конструктором копирования и оператором присваивания копии?
Конструктор копирования создаёт новый объект из существующего (T b = a;); оператор присваивания заменяет состояние уже построенного объекта (b = a;) и обязан обрабатывать самоприсваивание, обычно через copy-and-swap.
Типичные ошибки
- ✗Пропускать проверку самоприсваивания в operator= —
delete[] data; data = new ...(other.data)является UB приthis == &other - ✗Определять только одно из двух — если определён один, следует определить и другой (Правило пяти)
- ✗Возвращать
voidиз operator= — он должен возвращатьT&для поддержки цепочки (a = b = c)
Уточняющие вопросы
- →Объясните idiom copy-and-swap и почему он обеспечивает строгую гарантию безопасности исключений.
- →Когда компилятор генерирует неявный конструктор копирования и оператор присваивания копии?
MiddleТеорияОчень частоВ чём разница между перегрузкой и переопределением?
В чём разница между перегрузкой и переопределением?
Перегрузка — несколько одноимённых функций с разными типами параметров, разрешается при компиляции; переопределение — производный класс заменяет виртуальную функцию с той же сигнатурой, разрешается в рантайме через vtable.
Типичные ошибки
- ✗Объявлять функцию с тем же именем, но другой сигнатурой в производном — это скрывает перегрузки
Base, а не переопределяет их (используйтеusing Base::foo;для восстановления) - ✗Изменять только возвращаемый тип в производном классе и ожидать переопределения — тип возврата должен быть ковариантным или идентичным
- ✗Думать, что статические функции-члены можно переопределить — нельзя; они разрешаются во время компиляции
Уточняющие вопросы
- →Что такое правило сокрытия и как
usingего исправляет? - →Что означает ковариантный тип возврата в контексте переопределения?
JuniorТеорияЧастоКакой порядок конструирования и разрушения классов в иерархии? Порядок инициализации полей класса?
Какой порядок конструирования и разрушения классов в иерархии? Порядок инициализации полей класса?
Конструирование: сначала виртуальные базовые классы, затем невиртуальные (слева направо), затем поля (в порядке объявления), затем тело конструктора. Разрушение — строго обратное. Поля инициализируются в порядке объявления, а не в порядке списка инициализации.
Типичные ошибки
- ✗Писать список инициализации в порядке, отличном от порядка объявления, и ожидать инициализации в порядке списка — этого не происходит
- ✗Вызывать виртуальные функции в конструкторе базового класса, ожидая вызова производного переопределения — vptr указывает на базовый класс в этот момент
- ✗Забывать, что статические локальные переменные в конструкторах уничтожаются не так, как члены класса
Уточняющие вопросы
- →Что происходит, если исключение брошено в процессе конструирования?
- →Каков порядок разрушения членов класса и базового класса?
JuniorТеорияЧастоЧто такое глубокое копирование? Когда оно необходимо?
Что такое глубокое копирование? Когда оно необходимо?
Поверхностное копирование дублирует значения указателей — оба объекта владеют одной памятью и дают двойное освобождение; глубокое копирование выделяет новую память и дублирует данные, нужно когда класс владеет ресурсами кучи.
Типичные ошибки
- ✗Предоставлять только конструктор глубокого копирования без оператора глубокого присваивания — нарушение Правила трёх/пяти
- ✗Не использовать идиому copy-and-swap для присваивания — она безопасна при самоприсваивании и исключительно безопасна
- ✗Выполнять глубокое копирование там, где достаточна семантика перемещения — лишние выделения памяти
Уточняющие вопросы
- →Что такое идиома copy-and-swap и почему она безопасна по отношению к исключениям?
- →Как семантика перемещения снижает потребность в глубоком копировании?
JuniorТеорияЧастоКакие есть способы инициализации полей класса?
Какие есть способы инициализации полей класса?
Три способа: список инициализации членов в конструкторе (единственный для const/ссылочных полей и баз); инициализаторы членов по умолчанию (C++11); присваивание в теле.
Типичные ошибки
- ✗Присваивать в теле конструктора вместо списка инициализации для нетривиальных типов — вызывает лишнее конструирование
- ✗Не знать, что порядок инициализации следует порядку объявления, а не порядку в списке инициализации
- ✗Забывать, что ссылки и const-члены можно инициализировать только через список инициализации, не присваивать в теле
Уточняющие вопросы
- →Когда инициализаторы членов по умолчанию конфликтуют с записями списка инициализации?
- →Что такое агрегатная инициализация и когда можно использовать её без конструктора?
JuniorТеорияЧастоКак защитить объект от копирования?
Как защитить объект от копирования?
Объявите конструктор копирования и оператор копирующего присваивания как = delete; любая попытка копирования становится ошибкой компиляции. Для движимых, но не копируемых типов удаляются только операции копирования, move остаются.
Типичные ошибки
- ✗Использовать старую технику C++03 — объявлять операции копирования приватными — предпочтительнее
= deleteв C++11 и новее - ✗Забывать удалять оператор копирующего присваивания при удалении конструктора копирования
- ✗Не знать, что удаление операций копирования не удаляет автоматически операции перемещения
Уточняющие вопросы
- →В чём разница между удалёнными и приватными операциями копирования с точки зрения сообщений об ошибках?
- →Как
boost::noncopyableреализует то же самое и почему иногда предпочтительнее?
JuniorТеорияЧастоМожет ли pure virtual функция иметь реализацию? Что будет при её вызове из конструктора?
Может ли pure virtual функция иметь реализацию? Что будет при её вызове из конструктора?
Да, чисто виртуальная функция может иметь реализацию вне класса, вызываемую явно через Base::method(). Вызов через виртуальную диспетчеризацию из конструктора/деструктора — UB: vtable указывает на базу.
Типичные ошибки
- ✗Считать, что pure virtual означает 'реализация запрещена' — это значит 'производный обязан переопределить', а не 'тела быть не может'
- ✗Вызывать виртуальные функции (чисто виртуальные или нет) из конструктора, ожидая диспетчеризации к производному — её не будет
- ✗Не определять явно чисто виртуальный деструктор — компоновщик требует его
Уточняющие вопросы
- →Почему вызов виртуальной функции в конструкторе использует версию базового класса?
- →Какое практическое применение имеет реализация тела чисто виртуальной функции?
JuniorТеорияЧастоЧто делает ключевое слово static у функции-члена и в чём особенности статических полей класса?
Что делает ключевое слово static у функции-члена и в чём особенности статических полей класса?
Статическое поле принадлежит классу и разделяется всеми экземплярами; статическая функция-член не имеет this и может обращаться только к другим статическим членам класса.
Типичные ошибки
- ✗Определять статический член данных и в объявлении в заголовке, и в .cpp — приводит к ошибкам компоновщика или дублированию символов
- ✗Вызывать виртуальную функцию через статическую функцию-член — невозможно без объекта
- ✗Путать статический член класса со статической локальной переменной внутри функции — абсолютно разная семантика
Уточняющие вопросы
- →Где должен быть определён нестрочный статический член данных?
- →Когда уместно использовать статическую функцию-член вместо свободной функции в анонимном пространстве имён?
JuniorТеорияЧастоДля чего используют виртуальное наследование? Как можно решить проблему ромбовидного наследования?
Для чего используют виртуальное наследование? Как можно решить проблему ромбовидного наследования?
Проблема ромба: два базовых класса имеют общего предка, и производный класс получает два экземпляра. Виртуальное наследование оставляет один общий под-объект; наиболее производный класс конструирует виртуальный базовый класс напрямую, добавляя vbptr.
Типичные ошибки
- ✗Забывать, что наиболее производный класс обязан явно вызывать конструктор виртуального базового — вызовы промежуточных классов игнорируются
- ✗Не осознавать, что виртуальное наследование добавляет накладные расходы (vbptr, косвенный доступ к членам)
- ✗Использовать виртуальное наследование без необходимости — предпочтите композицию глубоким иерархиям наследования
Уточняющие вопросы
- →Что такое vbptr (указатель на виртуальный базовый класс) и когда он создаётся?
- →Как можно решить проблему ромба без виртуального наследования?
MiddleДебаггингЧастоПочему w = w; ломает этот operator=?
Почему w = w; ломает этот operator=?
При самоприсваивании (w = w;) оно удаляет data_, затем разыменовывает уже освобождённый other.data_ (тот же указатель) — use-after-free, UB. Решение: защита if (this != &other) или идиома copy-and-swap, которая безопасна и при самоприсваивании, и при исключениях.
Типичные ошибки
- ✗Считать, что самоприсваивание не случается в реальном коде
- ✗Думать, что проблема в отсутствующем операторе return
- ✗Полагать, что это утечка, а не использование освобождённой памяти
Уточняющие вопросы
- →Почему идиома copy-and-swap безопасна и при самоприсваивании, и при исключениях?
- →Как copy-and-swap переиспользует конструктор копирования и деструктор?
MiddleТеорияЧастоЧто такое = default и = delete?
Что такое = default и = delete?
= default просит компилятор сгенерировать реализацию по умолчанию специальной функции; = delete убирает функцию из набора перегрузок, и её вызов даёт ошибку компиляции.
Типичные ошибки
- ✗Путать
= defaultс пустым телом{}— разница:{}пользовательски определяет функцию (подавляя другие специальные),= default— реализация от компилятора - ✗Ставить
= deleteна функцию, которая никогда не вызывается — это важно только если функция может быть выбрана при разрешении перегрузки - ✗Не знать, что
= deleteприменяется к любой функции, а не только к специальным членам — удобно для блокировки конкретных перегрузок
Уточняющие вопросы
- →Что происходит с конструктором перемещения, если конструктор копирования объявлен как
= default? - →Как использовать
= delete, чтобы запретить вызов функции с конкретным типом аргумента?
MiddleТеорияЧастоЧто такое делегирующий конструктор?
Что такое делегирующий конструктор?
Делегирующий конструктор (C++11) вызывает другой конструктор того же класса в своём списке инициализации, избегая дублирования. Инициализаторы членов имеет только делегируемый; тело делегирующего выполняется после завершения делегации.
Типичные ошибки
- ✗Создавать цикл делегирования — компилятор обнаруживает это, но это запутанная ошибка
- ✗Смешивать делегирование с инициализаторами членов в одном списке инициализации — недопустимо; делегирующий конструктор не может иметь других инициализаторов членов
- ✗Не знать, что делегируемый конструктор полностью конструирует объект до выполнения тела делегирующего
Уточняющие вопросы
- →В чём разница между делегирующим конструктором и приватной вспомогательной функцией init?
- →Как делегирование взаимодействует с обработкой исключений в конструкторах?
MiddleТеорияЧастоЧто такое явное и неявное приведение типов в С++? Зачем делать explicit-конструктор?
Что такое явное и неявное приведение типов в С++? Зачем делать explicit-конструктор?
Конструктор с одним аргументом является преобразующим и допускает неявные преобразования; пометка explicit запрещает компилятору использовать его неявно — нужна прямая инициализация.
Типичные ошибки
- ✗Забывать
explicitу конструкторов с одним аргументом — приводит к трудноуловимым неявным преобразованиям - ✗Не знать, что
explicitтакже применяется к операторам преобразования (explicit operator bool()) - ✗Использовать
explicitу конструкторов с двумя и более аргументами — не имеет эффекта до C++11; с C++11 влияет на brace-init
Уточняющие вопросы
- →Для чего используется
explicit operator bool()? Приведите пример. - →Как
explicit(condition)в C++20 расширяет эту функциональность?
MiddleТеорияЧастоЧто такое friend и когда его следует использовать?
Что такое friend и когда его следует использовать?
friend даёт другому классу или функции полный доступ к private и protected членам; friend не наследуется, не транзитивна и не взаимна, обычно применяется для перегрузки операторов как не-членов.
Типичные ошибки
- ✗Объявлять
friendдля каждого класса, которому нужен доступ — признак проблемы дизайна; предпочтительнее геттеры/сеттеры или реструктуризация - ✗Думать, что friend наследуется — производный класс автоматически не получает дружеский доступ
- ✗Забывать forward-декларировать класс перед использованием его в объявлении friend в некоторых контекстах
Уточняющие вопросы
- →Как реализовать
operator<<для класса с приватными данными в виде дружественной функции? - →Есть ли способ дать частичный доступ к приватным членам без
friend?
MiddleТеорияЧастоЧто такое список инициализации? В чём его преимущество перед присваиванием в теле конструктора?
Что такое список инициализации? В чём его преимущество перед присваиванием в теле конструктора?
Список инициализации конструирует члены сразу с заданными значениями; присваивание в теле сначала конструирует по умолчанию, затем присваивает — две операции. Обязателен для const, ссылок и членов без default-конструктора.
Типичные ошибки
- ✗Писать список инициализации в порядке, отличном от порядка объявления членов — инициализация всегда происходит в порядке объявления
- ✗Пытаться инициализировать const-член в теле конструктора — запрещено
- ✗Инициализировать член с использованием другого члена, который ещё не инициализирован (проблема порядка)
Уточняющие вопросы
- →Когда предпочтительнее использовать инициализаторы членов по умолчанию в теле класса (C++11), а не список инициализации?
- →Какова разница в производительности между списком инициализации и присваиванием в теле для
std::string?
MiddleТеорияЧастоВ чём разница между интерфейсом и абстрактным классом в C++?
В чём разница между интерфейсом и абстрактным классом в C++?
В C++ нет ключевого слова interface — это соглашение: класс только с чистыми виртуальными функциями и без полей; абстрактный класс имеет хотя бы одну чистую виртуальную, но может содержать и поля, и конкретные методы.
Типичные ошибки
- ✗Пропускать виртуальный деструктор в интерфейсе — удаление объекта производного класса через указатель на интерфейс является UB
- ✗Добавлять поля данных в интерфейс — нарушает идиому чистого контракта и может привести к проблемам с расположением при алмазном наследовании
- ✗Помечать методы интерфейса как
final— запрещает переопределение и делает интерфейс бессмысленным
Уточняющие вопросы
- →Как смоделировать интерфейсы в стиле Java с множественным наследованием в C++?
- →Какова стоимость добавления нескольких чисто-виртуальных интерфейсов к классу?
MiddleТеорияЧастоМножественное наследование: плюсы, минусы и порядок конструирования при виртуальном наследовании.
Множественное наследование: плюсы, минусы и порядок конструирования при виртуальном наследовании.
Множественное наследование объединяет базовые классы, но даёт ромб (неоднозначность, дубли под-объектов); виртуальное наследование разделяет один под-объект — конструктор виртуального базового класса вызывает наиболее производный.
Типичные ошибки
- ✗Забывать, что при виртуальном наследовании наиболее производный класс обязан явно вызывать конструктор виртуального базового класса даже в глубоких иерархиях
- ✗Смешивать виртуальное и не-виртуальное наследование одного и того же базового класса — приводит к наличию и разделяемого виртуального под-объекта, и отдельной не-виртуальной копии
- ✗Обращаться к неоднозначному члену без квалификации — следует использовать
Base::memberдля устранения неоднозначности
Уточняющие вопросы
- →Каков порядок конструирования в
class D : virtual B1, virtual B2? - →Как виртуальное наследование влияет на размер и layout объектов?
MiddleТеорияЧастоЗачем использовать override? Что делает final у виртуальной функции?
Зачем использовать override? Что делает final у виртуальной функции?
override сообщает компилятору, что функция обязана переопределять виртуальную базового класса; если подходящей нет, компиляция падает — ловит молчаливые баги несовпадения сигнатур. final запрещает дальнейшее переопределение или наследование.
Типичные ошибки
- ✗Не писать
override— опечатка в сигнатуре создаёт новую не-виртуальную функцию вместо переопределения, молчаливо нарушая полиморфизм - ✗Думать, что
finalна функции подразумеваетoverride— они независимы; может потребоваться и то, и другое:void foo() override final - ✗Использовать
finalдля оптимизации диспетчеризации — хотя некоторые компиляторы девиртуализируютfinal-методы, это не гарантировано и не должно быть основной мотивацией
Уточняющие вопросы
- →Можно ли пометить не-виртуальную функцию как
override? Какую ошибку вы получите? - →Как
finalпомогает компилятору девиртуализировать вызовы?
MiddleТеорияЧастоКакие виды полиморфизма существуют в C++?
Какие виды полиморфизма существуют в C++?
Три основных вида: подтиповой (runtime) через виртуальные функции и vtable; параметрический (compile-time) через шаблоны; ситуативный через перегрузку функций и операторов. CRTP даёт статический подтип-полиморфизм.
Типичные ошибки
- ✗Считать полиморфизм виртуальных функций единственным видом — шаблоны и перегрузка не менее важны
- ✗Забывать, что статический полиморфизм (CRTP) требует знания всех подтипов во время компиляции — недоступен в runtime
- ✗Смешивать полиморфизм и наследование — это связанные, но разные концепции
Уточняющие вопросы
- →В каких ситуациях вы выберете CRTP вместо виртуальных функций?
- →Как
std::variantсstd::visitсоотносится с полиморфизмом?
MiddleДизайнЧастоВы ревьюите класс, который владеет ресурсом и объявляет только пользовательский деструктор. Объясните правило управления ресурсами Rule of Five — какие специальные функции-члены оно охватывает, когда классу действительно нужны все пять и когда можно не писать ни одной.
Вы ревьюите класс, который владеет ресурсом и объявляет только пользовательский деструктор. Объясните правило управления ресурсами Rule of Five — какие специальные функции-члены оно охватывает, когда классу действительно нужны все пять и когда можно не писать ни одной.
Если классу нужен пользовательский деструктор, копирующий конструктор или оператор присваивания, скорее всего нужны все пять. Использование RAII-членов (unique_ptr, vector) избавляет от их написания.
Типичные ошибки
- ✗Определение только деструктора и расчёт на генерируемый компилятором конструктор копирования — при наличии сырого ресурса приводит к двойному освобождению
- ✗Пропуск операций перемещения; компилятор может их не генерировать, если определён деструктор или копирование (правило нуля предпочтительнее)
- ✗Написание конструктора перемещения, который копирует ресурс вместо того, чтобы перемещать его
Уточняющие вопросы
- →Что такое правило нуля?
- →Чем =default отличается от отсутствия объявления специальной функции-члена?
MiddleДебаггингЧастоЧто такое срезка объектов (object slicing) и как её предотвратить?
Что такое срезка объектов (object slicing) и как её предотвратить?
Срезка происходит, когда производный объект копируется в базовый класс по значению — все производные члены теряются. Предотвращается ссылками, указателями, умными указателями или удалением конструктора копирования базового класса.
Открыть задачу →Типичные ошибки
- ✗Хранение полиморфных объектов в std::vector<Base> по значению — каждый элемент обрезается при вставке
- ✗Передача объекта производного класса в функцию, принимающую Base по значению
- ✗Присваивание производного объекта переменной базового класса, не осознавая, что это компилируется молча
Уточняющие вопросы
- →Почему std::vector<Base> вызывает срезку?
- →Как удаление конструктора копирования в абстрактном базовом классе предотвращает срезку?
MiddleТеорияЧастоЧто такое this и что произойдёт при обращении к static-члену через nullptr?
Что такое this и что произойдёт при обращении к static-члену через nullptr?
Нестатический метод получает скрытый this; static-члены не имеют this, и ptr->staticMethod() компилируется при null ptr (используйте Type::method()). Вызов не-static на nullptr — UB.
Типичные ошибки
- ✗Считать вызов метода безопасным только потому, что внутри явно не используются поля
- ✗Вызывать static-члены через указатель на объект вместо имени типа
- ✗Забывать, что this недоступен внутри static-методов
Уточняющие вопросы
- →Во что компилятор преобразует вызов нестатического метода?
- →Может ли this быть nullptr внутри корректного вызова нестатического метода?
MiddleТеорияЧастоКак работает виртуальная диспетчеризация и что такое vtable?
Как работает виртуальная диспетчеризация и что такое vtable?
Каждый полиморфный класс имеет статическую vtable (массив указателей на функции); каждый экземпляр содержит скрытый vptr. Виртуальный вызов разыменовывает vptr, индексирует vtable и переходит — побеждает наиболее производное переопределение.
Типичные ошибки
- ✗Вызов виртуальных функций в конструкторе или деструкторе — тип объекта ещё не полностью установлен, поэтому вызывается версия базового класса, а не наиболее производная
- ✗Ожидание виртуальной диспетчеризации через значение (срезка объектов удаляет производную часть)
- ✗Забывать, что виртуальная диспетчеризация требует как минимум одного уровня косвенности и по умолчанию не может быть встроена (inlined)
Уточняющие вопросы
- →Какова стоимость виртуального вызова на современных процессорах?
- →Как достичь полиморфизма без виртуальных функций (стирание типов, CRTP, std::variant)?
JuniorТеорияИногдаСколько памяти занимает объект пустого класса class A {}; ?
Сколько памяти занимает объект пустого класса class A {}; ?
sizeof(A) равен 1, а не 0 — стандарт требует уникальный адрес у каждого объекта. В качестве базового класса оптимизация EBO позволяет пустому под-объекту базового класса занимать нулевой размер.
Типичные ошибки
- ✗Считать, что
sizeof(EmptyBase)== 0 — для самостоятельных объектов это 1 - ✗Не знать об EBO — компиляторы применяют его, когда базовый класс пустой; это важно для нулевых накладных расходов классов-политик
- ✗Путать пустой класс с классом, содержащим только статические члены — статические члены не влияют на размер экземпляра
Уточняющие вопросы
- →Что такое
[[no_unique_address]](C++20) и как это связано с EBO? - →Почему sizeof всегда не менее 1 для любого типа в C++?
JuniorТеорияИногдаКак запретить наследовать класс? Что делает ключевое слово final?
Как запретить наследовать класс? Что делает ключевое слово final?
Поставьте final на класс (class C final {};), чтобы запретить дальнейшее наследование и позволить компилятору девиртуализировать вызовы. Также можно применять final к отдельным виртуальным функциям для запрета их переопределения.
Типичные ошибки
- ✗Не знать разницу между
finalна классе иfinalна виртуальной функции - ✗Использовать
private-наследование для предотвращения дальнейшего подклассирования — это не запрещает его;finalзапрещает - ✗Забывать, что
overrideиfinal— контекстно-зависимые идентификаторы, а не зарезервированные ключевые слова — их можно использовать как имена переменных (хотя не рекомендуется)
Уточняющие вопросы
- →Как компилятор может оптимизировать вызовы
finalвиртуальных функций? - →Что такое девиртуализация и когда она применяется?
JuniorТеорияИногдаЧто такое ключевое слово mutable и когда его использовать?
Что такое ключевое слово mutable и когда его использовать?
mutable разрешает изменять член через const-объект или внутри const-метода; типичное применение — ленивые кэши и мьютексы, которые нужно захватывать в read-only операциях.
Типичные ошибки
- ✗Применять
mutableдля членов, реально меняющих наблюдаемое состояние — это нарушает семантикуconst - ✗Забывать, что
mutableне влияет наconst-ссылки или указатели — только на нестатические члены данных - ✗Не объявлять мьютекс как
mutable— тогда его нельзя захватить внутриconst-метода
Уточняющие вопросы
- →Как реализовать потокобезопасный ленивый кэш с
mutable? - →Можно ли применить
mutableк статическому члену? Почему нет?
MiddleТеорияИногдаКакой порядок инициализации полей класса? Что случится, если конструктор инициализирует поля в другом порядке?
Какой порядок инициализации полей класса? Что случится, если конструктор инициализирует поля в другом порядке?
Поля инициализируются в порядке объявления, а не в порядке списка инициализации. Если список ссылается на поле, объявленное позже, оно читается неинициализированным — UB. Компиляторы предупреждают через -Wreorder.
Типичные ошибки
- ✗Писать
x(y + 1), y(0), когда y объявлен после x —yне инициализирован при конструированииx - ✗Не знать, что базовые классы всегда полностью конструируются перед любым членом производного класса
- ✗Игнорировать предупреждения компилятора
-Wreorder
Уточняющие вопросы
- →Как обеспечить правильный порядок инициализации, когда один член зависит от другого?
- →В чём опасность инициализации члена с использованием
this->otherMemberв списке инициализации?
MiddleТеорияИногдаКак компилятор различает переменные-члены и локальные переменные с одинаковым именем?
Как компилятор различает переменные-члены и локальные переменные с одинаковым именем?
Поиск имён идёт от внутренней области к внешней: локальные переменные перекрывают члены; для однозначности используйте this->name или ClassName::name, а соглашения вроде m_name/name_ снимают проблему.
Типичные ошибки
- ✗Писать
x = x;в теле конструктора, когда и параметр, и член называютсяx— виден только локальный параметр, присваивание является no-op - ✗Забывать, что
this->нужен внутри лямбд для захвата членов по имени (до C++20) - ✗Думать, что
ClassName::memberработает внутри не-статического метода безthis— только для статических членов
Уточняющие вопросы
- →Что такое ADL (argument-dependent lookup) и чем он отличается от поиска членов?
- →Чем поиск имён внутри тела шаблонного класса отличается от обычного класса?
MiddleДебаггингИногдаПочему удаление через этот Base* может привести к утечке?
Почему удаление через этот Base* может привести к утечке?
Удаление Derived через Base*, у которого деструктор не virtual, — неопределённое поведение: на практике ~Derived не вызывается, и ресурсы, которыми он владеет, утекают. Решение: дать полиморфному базовому классу virtual-деструктор (или владеть им через unique_ptr).
Типичные ошибки
- ✗Считать, что деструктор диспетчеризуется виртуально без объявления его virtual
- ✗Полагать, что деструктора базы достаточно, раз статический тип указателя — Base
- ✗Путать это со срезкой объекта — здесь объект цел, неверна только его очистка
Уточняющие вопросы
- →Когда базовому классу НЕ нужен виртуальный деструктор?
- →Как владение объектом через
std::unique_ptr<Base>устраняет эту ошибку?
MiddleТеорияИногдаДля чего используется приватное наследование?
Для чего используется приватное наследование?
Приватное наследование выражает «реализован через», а не «является»: public/protected члены базового класса становятся private. Применяется для переопределения virtual, EBO или доступа к protected; иначе предпочтите композицию.
Типичные ошибки
- ✗Путать приватное наследование с композицией — приватное наследование всё равно создаёт под-объект базового класса и может переопределять виртуальные функции; композиция не может
- ✗Использовать
using Base::member;внутри производного класса для восстановления доступа — это допустимо и иногда намеренно - ✗Думать, что приватное наследование запрещает проверку is-a —
static_castк базовому классу всё равно работает внутри класса
Уточняющие вопросы
- →Что такое Empty Base Optimisation и почему оно применяется только к (приватному) наследованию, а не к композиции?
- →Когда
using Base::foo;внутри приватно-производного класса имеет смысл?
MiddleДизайнИногдаВы проектируете базовый класс и думаете открыть часть его внутреннего состояния производным классам через protected, а не private. Объясните, когда protected-члены — оправданный выбор, и какие минусы они несут для инкапсуляции и дальнейшего сопровождения.
Вы проектируете базовый класс и думаете открыть часть его внутреннего состояния производным классам через protected, а не private. Объясните, когда protected-члены — оправданный выбор, и какие минусы они несут для инкапсуляции и дальнейшего сопровождения.
protected открывает доступ только производным классам — шире private, уже public. Применять осторожно: ослабляет инкапсуляцию в сторону производных классов, которыми вы не управляете.
Типичные ошибки
- ✗Делать всё
protected«на всякий» — внутренности утекают всем - ✗Путать доступ
protectedиprotected-наследование — разные механизмы - ✗Считать
protectedаналогом 'package-private' (это не так в C++)
Уточняющие вопросы
- →Что такое NVI (Non-Virtual Interface)?
- →Как
protectedвзаимодействует сfriend?
MiddleТеорияИногдаЧто такое статический полиморфизм в C++ и как он сравнивается с динамическим?
Что такое статический полиморфизм в C++ и как он сравнивается с динамическим?
Статический полиморфизм разрешается на этапе компиляции (шаблоны, перегрузка, CRTP) без рантайм-стоимости; динамический (virtual) разрешается в рантайме через vtable, поддерживает heterogeneous-контейнеры и runtime-типы.
Типичные ошибки
- ✗Выбирать virtual, когда тип известен в compile-time — оплата зря
- ✗Выбирать CRTP при паре типов — over-engineering
- ✗Смешивать оба стиля в одной иерархии без чёткого разделения
Уточняющие вопросы
- →Как CRTP даёт compile-time полиморфизм?
- →Когда C++23 deducing-this заменяет CRTP?
SeniorТеорияИногдаЧто такое оптимизация пустого базового класса и когда она применяется?
Что такое оптимизация пустого базового класса и когда она применяется?
Оптимизация пустого базового класса (EBO) позволяет базовому классу без нестатических полей занимать ноль байт внутри производного объекта вместо минимального 1 байта обычного пустого класса. Она применяется, только когда пустой базовый класс наследуется, а не хранится как член.
Типичные ошибки
- ✗Хранить функтор без состояния как член, а не базовый класс, платя лишний байт плюс выравнивание
- ✗Считать EBO гарантированной — она разрешена, но компилятор может отказаться в краевых случаях
- ✗Забывать, что два пустых базовых класса одного типа всё равно требуют разных адресов, блокируя EBO
Уточняющие вопросы
- →Как
[[no_unique_address]]достигает того же эффекта для полей данных? - →Почему
std::tupleопирается на EBO для своих пустых типов элементов?
SeniorДебаггингИногдаПочему порядок инициализации членов может вызывать неочевидные ошибки?
Почему порядок инициализации членов может вызывать неочевидные ошибки?
Нестатические поля данных инициализируются в порядке объявления, никогда в порядке их появления в списке инициализации. Если инициализатор одного поля читает другое поле, объявленное позже, то это поле ещё неинициализированный мусор. Включите -Wreorder, чтобы поймать это.
Типичные ошибки
- ✗Считать, что инициализацией управляет порядок списка инициализации, а не порядок объявления
- ✗Инициализировать одно поле из другого, объявленного позже, читая неинициализированный мусор
- ✗Игнорировать или отключать предупреждение
-Wreorderвместо исправления реального порядка
Уточняющие вопросы
- →Действует ли то же правило порядка для под-объектов базовых классов относительно членов?
- →Как перестроить класс, чтобы убрать зависимость инициализации между членами?
SeniorТеорияИногдаКак устроен в памяти класс с множественным наследованием и виртуальными функциями?
Как устроен в памяти класс с множественным наследованием и виртуальными функциями?
Каждый полиморфный базовый класс добавляет свой vptr; под-объекты идут подряд, приведение ко вторичному базовому корректирует указатель на смещение, а виртуальное наследование добавляет vbptr для общего под-объекта.
Типичные ошибки
- ✗Полагать, что
reinterpret_castработает корректно между базовым классом и производным при множественном наследовании — он игнорирует коррекцию смещения, в отличие отstatic_cast - ✗Думать, что
sizeof(Derived)равно сумме размеров базовых классов плюс члены производного — выравнивание и дополнительные указатели могут значительно увеличить его - ✗Забывать, что при виртуальном наследовании остаётся только один разделяемый под-объект базового класса, уменьшая размер по сравнению с не-виртуальным ромбом
Уточняющие вопросы
- →Как
dynamic_castобходит vtable для нахождения правильного под-объекта базового класса? - →Покажите приблизительный layout в памяти для
class D : public B1, public B2, где оба базовых класса имеют виртуальные функции.
SeniorТеорияИногдаЧто такое идиома невиртуального интерфейса (NVI) и зачем её использовать?
Что такое идиома невиртуального интерфейса (NVI) и зачем её использовать?
Идиома невиртуального интерфейса делает публичный интерфейс невиртуальным, а точки кастомизации — приватными virtual. Публичный метод выполняет общую до/после-логику и делегирует изменяемую часть приватному хуку, так что подклассы не могут пропустить обёрточный код.
Типичные ошибки
- ✗Делать хуки кастомизации публичными, что разрушает гарантию принудительной обёртки
- ✗Путать NVI с паттерном «Шаблонный метод» — NVI это C++-механизм управления доступом для него
- ✗Считать, что приватный virtual нельзя переопределить — производные классы всё равно могут его переопределить
Уточняющие вопросы
- →Как производный класс может переопределить приватный
virtual, который он даже не может вызвать? - →Как NVI упрощает добавление сквозной логики вроде логирования позже?
SeniorТеорияИногдаМожет ли чисто виртуальная функция иметь определение, и когда это полезно?
Может ли чисто виртуальная функция иметь определение, и когда это полезно?
Да — = 0 лишь помечает класс абстрактным и требует переопределений; это не запрещает определение, которое должно быть вне класса. Это полезно как общая реализация по умолчанию, вызываемая через Base::method(), и обязательно для чисто виртуального деструктора.
Типичные ошибки
- ✗Пытаться задать тело чисто виртуальной функции inline в объявлении — оно должно быть вне класса
- ✗Забыть определить чисто виртуальный деструктор, что вызывает ошибку компоновки
- ✗Считать, что определённая чисто виртуальная функция делает класс конкретным — он остаётся абстрактным
Уточняющие вопросы
- →Почему чисто виртуальный деструктор всегда должен иметь определение?
- →Как переопределение в производном классе вызывает реализацию чисто виртуальной функции по умолчанию из базы?
SeniorТеорияИногдаКакие специальные члены подавляет объявление деструктора или операции копирования?
Какие специальные члены подавляет объявление деструктора или операции копирования?
Объявление деструктора, конструктора копирования или копирующего присваивания подавляет неявную генерацию конструктора перемещения и перемещающего присваивания, поэтому класс молча копирует вместо перемещения. Объявление любой copy-операции также делает устаревшей другую copy-операцию.
Типичные ошибки
- ✗Добавить деструктор
~Base() = default;и не заметить, что перемещения молча стали копиями - ✗Считать генерацию копирования и перемещения независимыми — объявление одной copy-операции влияет на другую
- ✗Не применять правило пяти, когда хоть один специальный член объявлен пользователем
Уточняющие вопросы
- →Почему молчаливое копирование вместо перемещения — проблема производительности, а не корректности?
- →Чем
= defaultотличается от того, чтобы оставить специальный член необъявленным?
SeniorТеорияИногдаЧто делает класс «стандартно-компонуемым» (standard-layout) и почему это важно?
Что делает класс «стандартно-компонуемым» (standard-layout) и почему это важно?
Стандартно-компонуемый класс не имеет виртуальных функций и виртуальных базовых классов, все нестатические поля с одинаковым уровнем доступа, а поля объявлены не более чем в одном классе. Он имеет предсказуемый, не зависящий от компилятора layout и полностью совместим с C-структурами.
Типичные ошибки
- ✗Путать standard-layout с trivial — это независимые свойства, тип может иметь одно без другого
- ✗Смешивать
publicиprivateнестатические поля и ожидать, чтоoffsetofостанется определённым - ✗Добавить виртуальную функцию в структуру для C-взаимодействия и сломать её ABI-совместимость
Уточняющие вопросы
- →Чем standard-layout отличается от тривиального типа и от агрегата?
- →Что такое общая начальная последовательность и когда на неё безопасно полагаться?
SeniorДебаггингИногдаПочему виртуальный вызов внутри конструктора не диспетчеризуется к переопределению производного класса?
Почему виртуальный вызов внутри конструктора не диспетчеризуется к переопределению производного класса?
На каждой стадии конструирования vptr объекта указывает на vtable именно этого класса, потому что более производных частей ещё не существует. virtual-вызов из конструктора базового класса разрешается в версию базы. Вызов чисто виртуальной функции так — неопределённое поведение.
Типичные ошибки
- ✗Вызывать виртуальные хуки из конструктора базы, ожидая выполнения поведения производного класса
- ✗Вызывать чисто виртуальную функцию из конструктора или деструктора — UB, а не просто версия базы
- ✗Пытаться исправить это через
dynamic_cast<Derived*>(this)внутри конструктора базы
Уточняющие вопросы
- →Действует ли то же правило для виртуальных вызовов внутри деструктора, и почему?
- →Как двухфазная инициализация или фабричная функция чисто решают эту проблему?
SeniorКодИногдаНабросайте схему vtable и установку vptr для иерархии
Набросайте схему vtable и установку vptr для иерархии
Каждый полиморфный класс имеет статическую vtable; каждый экземпляр содержит скрытый vptr. Каждый конструктор сбрасывает vptr на vtable своего класса — virtual-вызовы в конструкторах не доходят до производных.
Типичные ошибки
- ✗Думать, что vtable принадлежит экземпляру — она принадлежит классу (статические данные)
- ✗Предполагать, что виртуальное наследование не вводит дополнительную сложность vptr
- ✗Использование reinterpret_cast для прямого чтения vptr — непереносимый ABI-хак
Уточняющие вопросы
- →Что такое чисто виртуальная функция и как она представлена в vtable?
- →Как множественное наследование влияет на структуру vtable?
JuniorТеорияРедкоВ чём разница между class, struct и union в C++?
В чём разница между class, struct и union в C++?
class и struct отличаются только умолчаниями: у class доступ private, у struct — public. union размещает все поля в одной области памяти — активным может быть только одно поле.
Типичные ошибки
- ✗Думать, что struct — это C-тип без методов, конструкторов и наследования
- ✗Читать неактивное поле union без управления временем жизни объекта
- ✗Использовать union там, где std::variant безопаснее выражает владение и активное состояние
Уточняющие вопросы
- →Как значения доступа по умолчанию влияют на наследование?
- →Когда union всё ещё уместен в современном C++?
JuniorКодРедкоНапишите класс Logger, который логирует в консоль или файл
Напишите класс Logger, который логирует в консоль или файл
Logger хранит std::ostream& и пишет строки с timestamp и уровнем. Принимать поток по ссылке позволяет передать std::cout, std::ofstream или любой ostream. Класс должен быть некопируемым, поскольку потоки не копируются.
Типичные ошибки
- ✗Хранить поток по значению вместо ссылки — потоки не копируются
- ✗Не сбрасывать буфер после каждой строки — последние сообщения могут быть потеряны при крэше
- ✗Игнорировать потокобезопасность — два потока, пишущих одновременно, дают перемешанный вывод; используйте
std::mutex
Уточняющие вопросы
- →Как добавить уровни логирования, чтобы
DEBUG-сообщения фильтровались в production? - →Как сделать Logger потокобезопасным с минимальной конкуренцией за блокировку?
JuniorТеорияРедкоКаковы основные принципы ООП?
Каковы основные принципы ООП?
Четыре принципа ООП: инкапсуляция (данные + методы, контроль доступа), абстракция (открывать только нужное), наследование (IS-A переиспользование), полиморфизм (общий интерфейс, разное поведение).
Типичные ошибки
- ✗Рассматривать наследование как основной механизм переиспользования — предпочитайте композицию; наследование для полиморфизма, а не переиспользования кода
- ✗Путать инкапсуляцию со скрытием данных — инкапсуляция касается управления доступом; можно иметь публичные данные (агрегатная структура) и всё равно быть 'инкапсулированным', если инварианты поддерживаются внешне
- ✗Реализовывать полиморфизм только через виртуальные функции — шаблоны и концепции обеспечивают бесплатный полиморфизм времени компиляции, который в C++ часто предпочтительнее
Уточняющие вопросы
- →Как C++ реализует полиморфизм времени выполнения внутри (vtable, vptr)?
- →В чём разница между ООП и дата-ориентированным дизайном (DOD) и когда DOD побеждает?
JuniorТеорияРедкоЧем отличаются pure-virtual класс, абстрактный класс и интерфейс в C++?
Чем отличаются pure-virtual класс, абстрактный класс и интерфейс в C++?
В C++ нет interface — это соглашение: только pure-virtual и virtual деструктор. Абстрактный класс: хотя бы одна pure-virtual, но может содержать данные и реализации.
Типичные ошибки
- ✗Забыть virtual destructor на интерфейсе — утечка при delete через base
- ✗Добавить данные в «интерфейс» — теряется смысл
- ✗Вызывать виртуальные методы из конструктора абстрактного базового класса
Уточняющие вопросы
- →Почему C++ использует множественное наследование вместо отдельного interface?
- →Как NVI работает с абстрактными классами?
MiddleКодРедкоСпроектируйте тестируемый сервис продуктов поверх БД
Спроектируйте тестируемый сервис продуктов поверх БД
Определите IProductRepository (чистый виртуальный fetchByFilter), SqlProductRepository для реальных запросов, FakeProductRepository для тестов. ProductService зависит только от интерфейса — Dependency Injection позволяет тесты без БД.
Типичные ошибки
- ✗Хардкодить вызов базы данных внутри сервиса — невозможно юнит-тестировать без реальной БД
- ✗Не делать деструктор интерфейса репозитория виртуальным — удаление через указатель на базу является UB
- ✗Возвращать сырые указатели из репозитория — используйте
std::vector<Product>по значению или умные указатели
Уточняющие вопросы
- →Как добавить пагинацию в
fetchByFilter? - →Как изменится этот дизайн, если репозиторий должен быть потокобезопасным?
MiddleКодРедкоРеализуйте игру «Жизнь» Конвея в ООП-стиле
Реализуйте игру «Жизнь» Конвея в ООП-стиле
Разделите на Grid (владеет клетками, считает поколение) и Printer/Renderer (рисует). Grid::step() считает соседей и применяет четыре правила через двойной буфер — пишет в новый grid и меняет местами — чтобы избежать багов мутации на месте.
Типичные ошибки
- ✗Обновлять клетки на месте — клетка, обновлённая на текущем шаге, влияет на подсчёт соседей позже на том же шаге
- ✗Хардкодить размеры поля в глобалах вместо параметров конструктора
- ✗Не обрабатывать граничные условия (крайние клетки имеют меньше 8 соседей)
Уточняющие вопросы
- →Как поддержать бесконечное (торовое) поле?
- →Как запустить
step()параллельно в нескольких потоках?
MiddleКодРедкоЧто выведет этот список инициализации членов?
Что выведет этот список инициализации членов?
s.b равно 5, но s.a — мусор. Члены инициализируются в порядке объявления (a, затем b), а не в порядке записи в списке инициализации, поэтому a(b) выполняется первым и читает b до его установки. Ловится сборкой с -Wreorder.
Типичные ошибки
- ✗Считать, что порядок в списке инициализации определяет инициализацию
- ✗Ожидать ошибку компиляции вместо чтения неинициализированного члена
- ✗Не знать, что
-Wreorderпредупреждает о расхождении списка и объявления
Уточняющие вопросы
- →Почему стандарт привязывает инициализацию к порядку объявления?
- →О чём именно предупреждает
-Wreorder?
MiddleКодРедкоНапишите корректный класс String по Правилу пяти
Напишите корректный класс String по Правилу пяти
String владеет char[] в куче и следует Правилу пяти: деструктор, copy-ctor, copy-assignment через copy-and-swap, move-ctor, move-assignment. Move обнуляет указатель источника против double-free.
Типичные ошибки
- ✗Забывать нулевой терминатор при вычислении размера —
new char[len_ + 1] - ✗Не обрабатывать самоприсваивание в operator= без copy-and-swap
- ✗Реализовывать конструктор перемещения без обнуления указателя источника — деструктор источника тогда выполняет double-free
Уточняющие вопросы
- →Как idiom copy-and-swap обеспечивает строгую гарантию безопасности исключений?
- →Что меняется при добавлении SSO (small string optimisation)?
SeniorТеорияРедкоВ каком порядке разрушаются базовые классы и члены, и почему деструктор должен быть виртуальным?
В каком порядке разрушаются базовые классы и члены, и почему деструктор должен быть виртуальным?
Разрушение идёт обратно конструированию: сначала тело деструктора производного класса, затем члены в обратном порядке объявления, затем базы. Деструктор базы должен быть virtual при удалении через указатель на базу — иначе производная часть утекает (UB).
Типичные ошибки
- ✗Удалять производный объект через указатель на базу, чей класс не имеет виртуального деструктора
- ✗Считать, что порядок разрушения совпадает с порядком объявления, а не обращает его
- ✗Думать, что виртуальный деструктор не нужен, если производный класс не добавляет полей — это всё равно UB
Уточняющие вопросы
- →Почему отсутствие виртуального деструктора — UB даже при тривиальном деструкторе производного класса?
- →Когда невиртуальный защищённый деструктор — правильный выбор дизайна вместо виртуального?