Переменные
Типы, lvalue/rvalue, const, constexpr и формы инициализации.
47 вопросов
JuniorТеорияОчень частоЧто делает auto? Автовывод возвращаемого типа, auto-параметры (C++20).
Что делает auto? Автовывод возвращаемого типа, auto-параметры (C++20).
auto выводит тип из инициализатора по правилам вывода аргументов шаблона; верхнеуровневые const/& отбрасываются — const auto& их сохраняет. C++14 добавил auto-возврат. decltype(auto) сохраняет точный тип. C++20 auto-параметры — сокращённые шаблоны.
Типичные ошибки
- ✗Писать
auto x = vec.begin();, затем изменятьvec—xстановится висячим итератором - ✗Использовать
autoдля proxy-типов вродеstd::vector<bool>::reference— даёт неожиданное поведение при присваивании - ✗Ожидать, что
autoвыводит ссылку:auto x = obj.getMember();копирует, даже еслиgetMember()возвращаетT&
Уточняющие вопросы
- →В чём разница между
auto,auto&,const auto&иauto&&? - →Когда предпочтительнее
decltype(auto)как тип возврата вместо обычногоauto?
JuniorТеорияОчень частоЧто такое enum? В чём разница между enum и enum class?
Что такое enum? В чём разница между enum и enum class?
enum (нескопированный) выводит перечислители в окружающий scope и неявно конвертируется в int. enum class (скопированный, C++11) держит их в своём scope и не конвертируется неявно — нужен static_cast. Оба позволяют задать базовый тип.
Типичные ошибки
- ✗Использовать перечислители нескопированного
enumв другой области видимости и получать конфликты имён - ✗Сравнивать значения двух разных
enum class— запрещено без приведения - ✗Забывать, что значения
enum classне выводятся автоматически — нуженto_stringилиoperator<<
Уточняющие вопросы
- →Как перебрать все значения
enum class? - →Каков базовый тип
enum classпо умолчанию без явного указания?
JuniorТеорияОчень частоКакие есть применения extern?
Какие есть применения extern?
extern имеет два применения: (1) объявление переменной или функции из другой TU — адрес разрешает компоновщик; (2) extern "C" отключает name mangling для C-интеропа. Без extern объявление переменной в namespace-scope — также определение, нарушая ODR между TU.
Типичные ошибки
- ✗Путать объявление
externс определением —extern int x;объявляет;int x;в глобальной области видимости определяет - ✗Забывать предоставить ровно одно определение при использовании
extern— ошибка компоновщика при нуле или нескольких определениях - ✗Применять
extern "C"к классу C++ или шаблону — невозможно; только свободные функции и POD-переменные
Уточняющие вопросы
- →Как
inline-переменные (C++17) устраняют необходимость в отдельномextern-объявлении в заголовках? - →В чём разница между
extern const int x;иconst int x;на уровне файла?
JuniorТеорияОчень частоЧто такое фундаментальные и встроенные типы в C++?
Что такое фундаментальные и встроенные типы в C++?
Фундаментальные типы: void, bool, символьные (char, wchar_t, char8/16/32_t), целочисленные (short/int/long/long long, знак/беззнак), вещественные (float, double, long double). Размеры определяются реализацией; <cstdint> даёт фиксированные псевдонимы.
Типичные ошибки
- ✗Считать, что
intвсегда 32 бита — гарантировано быть не менее 16 бит; используйтеint32_t, когда размер важен - ✗Применять арифметику
charбез приведения —charможет быть знаковым или беззнаковым в зависимости от платформы - ✗Смешивать арифметику знаковых и беззнаковых целых — неявное преобразование может давать неожиданные результаты
Уточняющие вопросы
- →В чём разница между
int8_t,int_fast8_tиint_least8_t? - →Когда вы использовали бы
long doubleвместоdouble?
JuniorТеорияОчень частоОбъясните lvalue, rvalue и xvalue. Как категория значения влияет на разрешение перегрузки?
Объясните lvalue, rvalue и xvalue. Как категория значения влияет на разрешение перегрузки?
lvalue — именованное адресуемое выражение, переживающее выражение. rvalue — временный без стабильного адреса. xvalue (истекающий) — именованный объект, приведённый к rvalue-ссылке через std::move. Перегрузка предпочитает T&& вместо const T& для rvalue, давая move.
Типичные ошибки
- ✗Путаница rvalue с «правой частью присваивания» — речь идёт о категории значения, а не о позиции
- ✗Мнение, что именованная rvalue-ссылка (T&&) является rvalue внутри тела функции — это lvalue
- ✗Использование std::move для возвращаемого значения, которое NRVO и так бы устранил
Уточняющие вопросы
- →В чём разница между prvalue и xvalue?
- →Почему std::move возвращает T&& и что это на самом деле делает?
JuniorТеорияОчень частоЧто такое const-указатель, указатель на const и ссылка? Чем отличаются const int и int const?
Что такое const-указатель, указатель на const и ссылка? Чем отличаются const int и int const?
const int и int const — один тип. С указателями: const T* переназначается, но значение read-only; T* const фиксирован, но значение изменяемо; const T* const запрещает оба. Ссылка — псевдоним без null, неперепривязываемый. Указатели обычно занимают машинное слово.
Типичные ошибки
- ✗Говорить, что
const intиint const— разные типы; это один и тот же тип - ✗Читать
const T*как 'const указатель на T' — const относится к тому, на что указывает, а не к самому указателю - ✗Привязывать не-const ссылку к временному объекту — запрещено; используйте
constссылку илиauto&&(C++11)
Уточняющие вопросы
- →Почему
const intиint constэквивалентны, аconst int*иint* const— нет? - →Что происходит с const-ссылкой, когда временный объект, к которому она привязана, должен был бы уничтожиться?
JuniorТеорияОчень частоКакие есть применения volatile? Когда его недостаточно?
Какие есть применения volatile? Когда его недостаточно?
volatile сообщает, что переменная может меняться вне контроля программы (железо, обработчики сигналов, setjmp); нельзя кэшировать или переупорядочивать чтения/записи. Не примитив синхронизации: межпоточного порядка нет. Для потокобезопасности — std::atomic.
Типичные ошибки
- ✗Использовать
volatileдля межпоточной коммуникации — стандарт не даёт гарантий порядка; используйтеstd::atomic - ✗Думать, что
volatileглобально отключает оптимизации компилятора — он отключает кэширование только для конкретной переменной - ✗Путать
volatileсconst volatileдля memory-mapped I/O — регистры оборудования только для чтения должны бытьconst volatile
Уточняющие вопросы
- →Может ли
volatile-переменная быть атомарной? Когда стоит совмещать оба квалификатора? - →В каких встраиваемых/ядерных контекстах
volatileпо-прежнему является правильным инструментом?
MiddleТеорияОчень частоВ чём разница между const и constexpr? Когда следует использовать каждый из них?
В чём разница между const и constexpr? Когда следует использовать каждый из них?
const — значение нельзя изменить через эту ссылку, но не обязано быть известно при компиляции. constexpr — должно быть вычислимо при компиляции и неявно const. constexpr — для compile-time констант; const — для runtime-неизменяемых.
Типичные ошибки
- ✗Ожидание, что локальные константы const всегда являются константами времени компиляции — это не так
- ✗Использование constexpr для функции, вызывающей не-constexpr функцию — не компилируется в constexpr-контексте
- ✗Путаница между constexpr и consteval (C++20): consteval принудительно вычисляется во время компиляции; constexpr вычисляется во время компиляции, когда это возможно
Уточняющие вопросы
- →Что такое consteval и чем он отличается от constexpr?
- →Может ли функция constexpr вызывать не-constexpr функции?
MiddleТеорияОчень частоЧто такое ссылка на временный объект? Как продлить его время жизни?
Что такое ссылка на временный объект? Как продлить его время жизни?
Временный живёт до конца полного выражения. Привязка const T& или T&& продлевает его до времени жизни ссылки. Продление не передаётся через параметры функции, init членов или std::move. Возврат локального через const& — UB.
Типичные ошибки
- ✗Передавать временный в функцию через
const T&и затем хранить эту ссылку — после возврата она висячая - ✗Думать, что
auto&& x = expr;всегда продлевает время жизни — продлевает только при прямой привязке к ref, не при вложенной - ✗Использовать const-ссылку на базу для временного объекта производного класса — время жизни продлевается, но срезание не предотвращается
Уточняющие вопросы
- →Что происходит при привязке временного к ссылочному члену в списке инициализации конструктора?
- →Чем
std::reference_wrapperотличается от обычной ссылки с точки зрения времени жизни?
JuniorТеорияЧастоКак представлены числа с плавающей точкой и почему опасно сравнивать их через ==?
Как представлены числа с плавающей точкой и почему опасно сравнивать их через ==?
Большинство float/double используют IEEE 754: бит знака, смещённую экспоненту, мантиссу. Многие десятичные дроби не представимы точно в двоичном виде, поэтому арифметика накапливает ошибку округления; == ненадёжно.
Типичные ошибки
- ✗Ожидать, что 0.1 + 0.2 == 0.3
- ✗Использовать один универсальный epsilon для значений разных порядков
- ✗Игнорировать NaN и infinity в парсинге или математическом коде
Уточняющие вопросы
- →Чем отличается абсолютный и относительный epsilon?
- →Как NaN ведёт себя в сравнениях?
JuniorТеорияЧастоЧто такое инициализация переменной внутри if?
Что такое инициализация переменной внутри if?
C++17 разрешает init-statement в if/switch: if (auto it = map.find(key); it != map.end()) { ... }. Переменная видна во всём блоке if/else. Не засоряет окружающий scope, удобно с RAII-guard.
Типичные ошибки
- ✗Пытаться использовать переменную из init-statement после всего блока if/else — она вне области видимости
- ✗Забывать точку с запятой-разделитель:
if (init; condition)— её отсутствие является ошибкой компиляции - ✗Использовать это в компиляторах до C++17 без проверки совместимости
Уточняющие вопросы
- →Как init-statement в
ifвзаимодействует со structured bindings в C++17? - →Можно ли использовать
try/catchкак init-statement? Почему нет?
JuniorТеорияЧастоВ чём разница между int8_t и char? Когда имеет значение знаковость?
В чём разница между int8_t и char? Когда имеет значение знаковость?
int8_t — тип с точной шириной в 8 бит со знаком, гарантированный стандартом. char может быть знаковым или беззнаковым в зависимости от платформы и флагов компилятора.
Типичные ошибки
- ✗Предположение, что char всегда знаковый — MSVC и многие ARM-платформы по умолчанию используют unsigned char
- ✗Использование char для арифметики, а затем сравнение с -1 — скрытые ошибки знакового расширения
- ✗Трактовка char и uint8_t как свободно взаимозаменяемых
Уточняющие вопросы
- →Когда std::byte — правильный выбор?
- →Что такое -funsigned-char и когда его стоит устанавливать?
JuniorТеорияЧастоЧто такое namespace? Анонимное пространство имён? Доступ к вложенным пространствам.
Что такое namespace? Анонимное пространство имён? Доступ к вложенным пространствам.
Namespace группирует связанные имена против конфликтов. Анонимное даёт содержимому внутреннюю компоновку — как static на уровне файла, но и для типов/классов. К вложенным — через :: или using. C++17 разрешает namespace A::B::C { ... }.
Типичные ошибки
- ✗Размещать
using namespaceна уровне файла в заголовке — загрязняет всех включающих - ✗Путать анонимное пространство имён с именованным, время жизни которого ограничено одной TU
- ✗Не знать, что
namespaceможно повторно открывать и расширять в нескольких файлах
Уточняющие вопросы
- →Что такое inline namespace и как оно используется для версионирования ABI?
- →Как правила ADL взаимодействуют с пространствами имён?
JuniorТеорияЧастоКак получить минимальное/максимальное значение типа в C++?
Как получить минимальное/максимальное значение типа в C++?
Используйте std::numeric_limits<T> из <limits>: ::max(), ::lowest(). Для целых min() — наиболее отрицательное; для вещественных — наименьшее положительное нормализованное, для наиболее отрицательного — lowest(). C-макросы вроде INT_MAX работают, но не обобщены.
Типичные ошибки
- ✗Использовать
std::numeric_limits<float>::min()для получения наиболее отрицательного float — это наименьшее положительное значение; используйтеlowest() - ✗Не включать
<limits>перед использованиемstd::numeric_limits - ✗Использовать numeric_limits с неспециализированными типами — специализация по умолчанию возвращает нули
Уточняющие вопросы
- →Что означает
std::numeric_limits<T>::is_integer? - →Как во время компиляции проверить, может ли тип представить конкретное значение?
JuniorТеорияЧастоЧем range-for отличается от традиционного for?
Чем range-for отличается от традиционного for?
Range-for — сахар над begin()/end(): продвигает итератор до end(). Работает для типов с begin/end (методы или свободные через ADL). Без std::views::enumerate (C++23) индекса нет; изменение контейнера инвалидирует итератор (UB).
Типичные ошибки
- ✗Использовать
autoвместоauto&— копирует каждый элемент, медленно для больших объектов - ✗Использовать
auto&на временном диапазонном выражении — диапазон уничтожается до конца итерации (висячая ссылка) - ✗Удалять/вставлять элементы внутри range-for — инвалидирует внутренний итератор
Уточняющие вопросы
- →Как сделать пользовательский класс совместимым с range-for?
- →Что даёт
for (auto&& x : range)по сравнению сfor (auto& x : range)?
JuniorТеорияЧастоКак static влияет на глобальные и локальные переменные?
Как static влияет на глобальные и локальные переменные?
На локальной static даёт статическое время хранения: инициализируется один раз при первом проходе и живёт до завершения программы. На глобале или функции меняет внешнюю компоновку на внутреннюю — невидим вне TU. Два разных применения.
Типичные ошибки
- ✗Считать статические локальные переменные потокобезопасными до C++11 — с C++11 инициализация гарантированно потокобезопасна, но последующий доступ — нет
- ✗Использовать
staticдля глобальной переменной, чтобы 'сэкономить память' — это изменяет компоновку, а не время жизни или размер - ✗Забывать, что деструктор статической локальной переменной выполняется в обратном порядке инициализации при завершении программы — осторожнее с взаимными зависимостями
Уточняющие вопросы
- →Что такое SIOF (static initialisation order fiasco) и как статические локальные переменные помогают его избежать?
- →Что происходит, если конструктор
static-локальной переменной выбрасывает исключение?
JuniorТеорияЧастоКак определить размер структуры в C++?
Как определить размер структуры в C++?
sizeof(T) возвращает размер с padding. Компилятор выравнивает каждое поле по его выравниванию и добавляет trailing padding, чтобы элементы массива оставались выровнены; итог кратен наибольшему выравниванию struct. offsetof и #pragma pack — для инспекции и контроля layout.
Типичные ошибки
- ✗Считать, что
sizeof(S)равен сумме размеров членов — padding может добавить несколько байт - ✗Переставлять члены без учёта выравнивания — размещайте более крупные члены первыми для минимизации потерь
- ✗Применять
sizeofк указателю на структуру — возвращает размер указателя, а не структуры
Уточняющие вопросы
- →Как
alignasпозволяет переопределить выравнивание типа по умолчанию? - →В чём разница между
sizeofиalignof?
MiddleТеорияЧастоЧто такое aggregate-тип и как работает aggregate-инициализация?
Что такое aggregate-тип и как работает aggregate-инициализация?
Aggregate — класс без пользовательских конструкторов, без private/protected полей и без virtual-функций или virtual-баз; T x{a,b,c}; инициализирует члены в порядке объявления, неперечисленные получают value-инициализацию.
Типичные ошибки
- ✗Добавить конструктор в struct, используемой для aggregate-init, —
T{a, b}ломается - ✗Полагаться на порядок объявления членов — добавление поля ломает все
T{} - ✗Смешивать designated и позиционные initializers — в C++20 запрещено
Уточняющие вопросы
- →Как C++17 изменил правила aggregate, разрешив публичные базовые классы?
- →Когда CTAD работает для aggregate?
MiddleТеорияЧастоКаковы правила вывода типов для auto? Когда оно может неожиданно копировать?
Каковы правила вывода типов для auto? Когда оно может неожиданно копировать?
auto следует правилам вывода шаблонов: ссылки и верхнеуровневый const убираются; массивы и функции распадаются в указатели. Поэтому auto x = v[0]; копирует даже если выглядит легковесно. decltype(auto) сохраняет точный объявленный тип, включая ссылки.
Типичные ошибки
- ✗Писать
auto x = heavyObject.get();— снимает ссылку, возвращённуюget(), и создаёт полную копию - ✗Использовать
autoсstd::vector<bool>— выводитstd::vector<bool>::reference, прокси-тип, а неbool - ✗Ожидать, что
autoсохраняет cv-квалификацию выражения — верхнеуровневый const всегда убирается
Уточняющие вопросы
- →Какие три варианта вывода
autoсуществуют (значение, ссылка, forwarding-reference) и какие правила обрезки у каждого? - →Когда
decltype(auto)является правильным выбором для типа возврата?
MiddleТеорияЧастоЧто такое const-корректность и почему это важно?
Что такое const-корректность и почему это важно?
Const-корректность — пометка каждой переменной, параметра и метода, не меняющих состояние, как const. Ловит случайные мутации, даёт бесплатные const&-параметры; на const-объекте можно вызвать только const-квалифицированные методы.
Типичные ошибки
- ✗Забывать
constу геттеров — тогда их нельзя вызвать наconst-объектах или черезconst-ссылки - ✗Отбрасывать
constчерезconst_castдля обхода архитектурного недостатка — правильное решение — добавитьconst-перегрузку - ✗Не помечать параметры
const T&, когда функция их не изменяет — приводит к ненужным копиям или несовместимым местам вызова
Уточняющие вопросы
- →Как предоставить и
const, и не-const перегрузку геттера без дублирования кода? - →В чём разница между
const-методом и методом с[[nodiscard]]?
MiddleТеорияЧастоЧто выводит decltype(expr) и чем оно отличается от auto?
Что выводит decltype(expr) и чем оно отличается от auto?
decltype(name) — точно объявленный тип (со ссылками/cv). decltype((name)) добавляет &, ведь name в скобках — lvalue. auto снимает ссылки и top-level cv, если не добавить &/const. decltype(auto) (C++14) выводит по правилам decltype.
Типичные ошибки
- ✗Забывать, что
decltype((x))даётT&, неT— типовая ловушка - ✗Использовать
autoдля проброса возврата и терять ссылку - ✗Смешивать
decltype(auto)иauto&&без понимания разницы
Уточняющие вопросы
- →Чем
auto&&отличается отdecltype(auto)в возвратах? - →Когда
decltype(auto)даёт неверный результат?
MiddleТеорияЧастоПочему пустая структура занимает 1 байт? Что такое минимально адресуемая единица?
Почему пустая структура занимает 1 байт? Что такое минимально адресуемая единица?
Каждый отдельный объект должен иметь уникальный адрес; sizeof == 0 позволил бы двум объектам делить адрес. Поэтому стандарт требует sizeof(T) >= 1. Исключение — Empty Base Optimisation: пустой класс как базовый может занимать ноль внутри производного объекта.
Типичные ошибки
- ✗Путать EBO с объектами нулевого размера как самостоятельными — EBO применяется только к под-объектам баз, а не к отдельным экземплярам
- ✗Не знать, что
[[no_unique_address]](C++20) даёт аналогичную EBO экономию размера и для членов-переменных - ✗Проверять
sizeofбазового класса для предсказания, сколько он добавит к производному — EBO может полностью устранить затраты
Уточняющие вопросы
- →Чем
[[no_unique_address]]отличается от EBO на практике? - →Когда вы намеренно использовали бы пустую структуру как тег-тип?
MiddleТеорияЧастоКак компилятор решает padding внутри struct и как его минимизировать?
Как компилятор решает padding внутри struct и как его минимизировать?
Каждый член на смещении, кратном alignof(T); padding заполняет дыры, struct выровнена по наибольшему. Largest-first уменьшает. [[no_unique_address]] (C++20) даёт пустым членам общее хранилище; #pragma pack переопределяет выравнивание.
Типичные ошибки
- ✗Везде ставить
#pragma pack(1)— производительность падает - ✗Считать размер вручную без учёта padding —
sizeofединственная истина - ✗Забывать, что alignment отличается между компиляторами/архитектурами
Уточняющие вопросы
- →Как
[[no_unique_address]]экономит место для пустых типов? - →Почему packed-доступ дороже на ARM, чем на x86?
MiddleТеорияЧастоВсе применения static в C++. Что такое static initialisation order fiasco (SIOF)?
Все применения static в C++. Что такое static initialisation order fiasco (SIOF)?
static имеет четыре применения: (1) статическое хранение локальных (ленивая init); (2) внутренняя компоновка глобалов/функций; (3) члены уровня класса; (4) static-local в методах. SIOF: порядок init нелокальных статиков между TU не определён — A может использовать неинициализированный B.
Типичные ошибки
- ✗Определять нетривиальный глобальный объект, зависящий от глобального из другой TU — классическая причина SIOF
- ✗Думать, что статические члены класса автоматически определяются — их нужно определить ровно в одном
.cpp(кромеinlineв C++17) - ✗Путать три разных смысла
staticпри чтении незнакомого кода
Уточняющие вопросы
- →Как паттерн Meyers singleton гарантирует, что инициализация произойдёт перед первым использованием?
- →Подчиняется ли инициализация
constexpr-глобального SIOF?
MiddleТеорияЧастоЧто такое structured bindings (C++17) и что они умеют распаковывать?
Что такое structured bindings (C++17) и что они умеют распаковывать?
auto [a, b, c] = expr; распаковывает tuple-подобное (aggregate, std::pair, std::tuple или класс с get<I> + tuple_size) в именованные переменные. auto& — ссылки, const auto& — const-view. Тип каждой выводится из источника.
Типичные ошибки
- ✗Забыть ссылку и копировать каждый элемент при итерации по map
- ✗Пытаться разобрать struct с не-public членами — нужен aggregate или tuple-like протокол
- ✗Использовать structured bindings для распаковки в члены класса — нельзя
Уточняющие вопросы
- →Как сделать свой класс распаковываемым через tuple-protocol?
- →Чем
auto [a, b]отличается отauto& [a, b]?
MiddleТеорияЧастоЧто такое thread_local storage и сколько это стоит?
Что такое thread_local storage и сколько это стоит?
thread_local T x; даёт каждому потоку свой x, инициализируемый при первом обращении и уничтожаемый при выходе. Применения: per-thread кеши, RNG, error state. Цена: indirection на доступ (TLS slot); shared-библиотека — __tls_get_addr.
Типичные ошибки
- ✗Инициализировать thread_local cross-thread данными — гонка при первом обращении
- ✗Забыть, что деструктор бежит при выходе потока; потоки от
pthread_createмогут не вызвать - ✗Хранить много в thread_local — память умножается на число потоков
Уточняющие вопросы
- →Чем
thread_localотличается от__thread? - →Как TLS работает с
dlopenshared-библиотек?
MiddleТеорияЧастоЧто такое неопределённое поведение (UB)? Приведите примеры.
Что такое неопределённое поведение (UB)? Приведите примеры.
UB — стандарт не ограничивает поведение программы; компилятор считает, что UB не возникает, и оптимизирует. Примеры: знаковое переполнение, разыменование null/висячего, выход за границы, неинициализированные чтения, гонки. Не runtime-ошибка: debug может пройти, release — упасть.
Типичные ошибки
- ✗Считать, что UB 'работает на практике' — оптимизаторы используют UB для удаления ветвей, создавая уязвимости безопасности
- ✗Думать, что переполнение знакового целого даёт wrap-around — у беззнаковых есть, у знаковых — UB
- ✗Не использовать санитайзеры (
-fsanitize=undefined,address) — UB невидимо без них
Уточняющие вопросы
- →Что такое поведение, определяемое реализацией, и чем оно отличается от UB?
- →Как компиляторы используют UB для обоснования удаления проверок на null или границ цикла?
MiddleТеорияЧастоЧто такое uniform initialisation? Что такое агрегатная инициализация?
Что такое uniform initialisation? Что такое агрегатная инициализация?
Uniform init (C++11) использует {} повсюду и запрещает сужение (int x{3.14} — ошибка). Агрегатная init применима, если у класса нет user-ctor, нет приватных/защищённых нестатических членов, нет виртуальных функций и непубличных баз — T obj{a, b, c} заполняет члены в порядке без ctor.
Типичные ошибки
- ✗Неожиданный вызов конструктора
std::initializer_list—vector<int> v{3}создаёт вектор из 1 элемента, а не из 3 - ✗Думать, что агрегатная инициализация работает для классов с любым пользовательским конструктором — даже defaulted-конструктор дисквалифицирует до C++20
- ✗Забывать, что
{}выполняет нулевую инициализацию встроенных типов:int x{}равно 0, в отличие отint x;, которое неопределённо
Уточняющие вопросы
- →Как назначенная инициализация (C++20) улучшает агрегатную инициализацию?
- →В чём разница между
T{}иT()для типа-класса?
MiddleТеорияЧастоЧем отличаются default-, value- и zero-initialisation?
Чем отличаются default-, value- и zero-initialisation?
Default-init T x; оставляет встроенные с неопределённым значением (UB читать) и вызывает default-ctor у классов. Value-init T x{}/T() обнуляет встроенные; для класса без user-ctor зануляет члены, затем зовёт сгенерированный ctor. Zero-init — отдельная стадия для namespace-scope объектов.
Типичные ошибки
- ✗Писать
int x;и читать — UB; используйтеint x{};для нуля - ✗Забывать, что
T x();— объявление функции, не value-init (most vexing parse) - ✗
= {}копирующая инициализация (но с пустым списком) — иногда удивляет
Уточняющие вопросы
- →Что делает
T()для класса с user-provided default ctor? - →Почему uniform
{}предпочтительнее для безопасности?
SeniorТеорияЧастоЧем различаются constexpr, consteval и constinit?
Чем различаются constexpr, consteval и constinit?
constexpr означает, что функция может выполниться при константном вычислении — либо в runtime, в зависимости от аргументов. consteval (C++20) делает функцию немедленной: каждый вызов обязан вычисляться на этапе компиляции. constinit (C++20) форсирует инициализацию статической переменной на этапе компиляции, убивая фиаско порядка инициализации, но переменная остаётся изменяемой.
Типичные ошибки
- ✗Думать, что
constexprгарантирует вычисление на этапе компиляции — только в контексте константного вычисления - ✗Ожидать, что
constinitсделает переменную пригодной в константных выражениях — он не подразумеваетconst - ✗Вызывать
consteval-функцию с runtime-аргументом и недоумевать из-за жёсткой ошибки компиляции
Уточняющие вопросы
- →Когда выбрать
constinitвместоconstexprдля глобальной переменной? - →Может ли
consteval-функция вызватьconstexpr-функцию и наоборот?
SeniorТеорияЧастоЧто такое сужающее преобразование и где C++ его запрещает?
Что такое сужающее преобразование и где C++ его запрещает?
Сужающее преобразование — это неявное преобразование, способное потерять информацию: из плавающей точки в целое, из более широкого типа в более узкий, либо значение, непредставимое в цели. C++ запрещает сужение внутри brace-инициализации: int x{3.5}; некорректен, тогда как int x = 3.5; компилируется.
Типичные ошибки
- ✗Считать
{}-инициализацию чисто косметической — она добавляет диагностику сужения, которой нет у= - ✗Полагать, что
longвintвнутри скобок безвреден — это сужение, если значение не подходящая константа - ✗Забывать, что усечение
doubleвint— сужение, поэтомуint v{computeRatio()};не компилируется
Уточняющие вопросы
- →Почему сужение из укладывающегося константного выражения явно разрешено?
- →Как обнаружение сужения взаимодействует с
auto x{...}?
SeniorТеорияЧастоЧто такое std::variant и std::any? Когда использовать каждый?
Что такое std::variant и std::any? Когда использовать каждый?
std::variant<A,B,C> — типобезопасное теговое объединение, хранящее одно значение из фиксированного compile-time набора; доступ через std::get<T> или std::visit. std::any — type-erased контейнер с открытым в runtime набором. variant — для известных альтернатив; any — для динамических типов.
Типичные ошибки
- ✗Использовать
std::anyтам, где подошёл быstd::variant— variant безопаснее, быстрее и не требует выделения в куче - ✗Забывать, что
std::get<T>(v)бросаетstd::bad_variant_access, еслиvне хранитT— используйтеstd::get_ifдля не-бросающего доступа - ✗Считать
std::anyбесплатным — удаление типа и small-buffer optimisation имеют накладные расходы; бенчмаркируйте при использовании в горячем пути
Уточняющие вопросы
- →Как
std::visitс лямбда-обработчиком заменяет цепочкуif/dynamic_cast? - →Что такое
std::monostateи зачем его помещать вvariant?
JuniorКодИногдаЧто выведут c, c + 1 и char(c + 1)?
Что выведут c, c + 1 и char(c + 1)?
A 66 B. c печатается как символ A. В c + 1 тип char повышается до int, поэтому результат — 66, печатается как число. Приведение обратно char(c + 1) снова делает его char, и поток печатает 'B' (ASCII 66).
Типичные ошибки
- ✗Думать, что арифметика над char сохраняет тип char
- ✗Считать, что вывод char печатает его числовой код
- ✗Упускать, что явное приведение к char меняет форматирование в потоке
Уточняющие вопросы
- →Почему
std::cout << cпечатает букву, аstd::cout << c + 1— число? - →К какому целому типу повышается
charв арифметике?
JuniorКодИногдаЧто выведут 5 / 2, 5.0 / 2 и 7 % 3?
Что выведут 5 / 2, 5.0 / 2 и 7 % 3?
2 2.5 1. 5 / 2 — целочисленное деление, усекающее к нулю. 5.0 / 2 повышает int до double, давая 2.5. % — целочисленный остаток. Учтите: -7 % 3 в C++ равно -1 — знак результата следует за делимым.
Типичные ошибки
- ✗Ожидать, что
5 / 2даст2.5, потому что математический результат дробный - ✗Думать, что
%округляет частное, а не возвращает остаток - ✗Считать, что
-7 % 3равно2— в C++ знак следует за делимым, поэтому это-1
Уточняющие вопросы
- →Чему равно
-7 % 3в C++ и почему? - →Как заставить деление двух операндов
intидти в плавающей точке?
JuniorКодИногдаЧто выведет i = i++ + ++i;?
Что выведет i = i++ + ++i;?
Неопределённое поведение: i изменяется дважды (i++ и ++i) и читается без точки следования между побочными эффектами, поэтому стандарт не задаёт результат — разные компиляторы печатают разное, а программа невалидна.
Типичные ошибки
- ✗Вычисление одного «правильного» значения, как будто операции упорядочены слева направо
- ✗Называть это неуточнённым (один из допустимых выводов), а не неопределённым (невалидная программа)
- ✗Предполагать, что новый стандарт задаёт результат
Уточняющие вопросы
- →В чём разница между неуточнённым и неопределённым поведением?
- →Какие выражения в C++17 получили гарантированный порядок вычисления?
MiddleТеорияИногдаЧто такое битовые поля? Применения и правила layout.
Что такое битовые поля? Применения и правила layout.
Битовое поле — член с двоеточием и количеством бит: unsigned int flags : 3;. Компилятор упаковывает их в блоки. Порядок внутри блока определяется реализацией; безымянное поле нулевой ширины открывает новый блок; адрес битового поля взять нельзя.
Типичные ошибки
- ✗Считать layout битового поля переносимым между компиляторами и порядком байт — это не так; для кросс-платформенных протоколов используйте ручную битовую маскировку
- ✗Использовать знаковые битовые поля без явного
signed— знаковость обычногоint-битового поля определяется реализацией - ✗Брать адрес битового поля — запрещено; используйте временную переменную
Уточняющие вопросы
- →В чём разница между
int x : 0;и полным отсутствием поля? - →Когда предпочтительнее
std::bitsetили ручная битовая маскировка вместо битовых полей?
MiddleКодИногдаЧто выведет std::cout << i++ << i++ << i++?
Что выведет std::cout << i++ << i++ << i++?
До C++14 включительно порядок вычисления операндов << был неуточнённым, поэтому могло печататься 2 1 0 или 0 1 2. C++17 сделал операнды << вычисляемыми слева направо, поэтому теперь надёжно печатается 0 1 2. Порядок аргументов функций всё ещё неуточнён — избегайте таких побочных эффектов.
Типичные ошибки
- ✗Считать, что цепочка
<<всегда вычислялась слева направо до C++17 - ✗Называть это неопределённым поведением — оно неуточнённое, ведь каждый
i++упорядочен в рамках отдельного вызова<< - ✗Обобщать исправление C++17 на все аргументы функций
Уточняющие вопросы
- →В чём здесь разница между неуточнённым порядком вычисления и неопределённым поведением?
- →Применяется ли правило C++17 «слева направо» к обычным аргументам функций?
MiddleТеорияИногдаЧто такое most vexing parse и как его избежать?
Что такое most vexing parse и как его избежать?
Когда объявление можно прочитать как функцию или инициализацию, C++ выбирает функцию. T x(MyArg()); похоже на конструирование, но парсится как функция x, принимающая указатель на функцию, возвращающую MyArg. Лечение — T x{MyArg{}}; или именованный аргумент.
Типичные ошибки
- ✗Писать
Foo a();для default-сконструированного Foo — это объявление функции - ✗Дебажить «у Foo нет метода X», когда это на самом деле указатель на функцию
- ✗Не использовать uniform initialisation по умолчанию, чтобы избегать ловушки
Уточняющие вопросы
- →Почему язык склоняется к функциям при разрешении неоднозначности?
- →Как C++11 brace init закрыл эту дыру?
MiddleДебаггингИногдаПочему этот цикл суммы квадратов может быть неопределённым?
Почему этот цикл суммы квадратов может быть неопределённым?
Арифметика int переполняется на больших квадратах, а знаковое переполнение — это неопределённое поведение (а не перенос), поэтому и a[i] * a[i], и накапливаемый int sum могут молча переполниться. Решение: более широкий тип — long long sum и static_cast<long long>(a[i]) * a[i].
Типичные ошибки
- ✗Считать, что знаковое переполнение переносится как беззнаковое, а не является UB
- ✗Полагать, что умножение автоматически повышается до double
- ✗Винить индекс цикла вместо ширины накопителя
Уточняющие вопросы
- →Почему знаковое целочисленное переполнение — это UB, а беззнаковое переносится?
- →Почему
a[i]нужно приводить кlong longдо умножения, а не после?
MiddleКодИногдаЧто выведет это сравнение знакового и беззнакового?
Что выведет это сравнение знакового и беззнакового?
0 (ложь). В смешанном сравнении int преобразуется к unsigned, поэтому -1 становится огромным значением (UINT_MAX), которое не меньше 1. Эта ловушка обычных арифметических преобразований ломает и for (size_t i = v.size() - 1; i >= 0; --i).
Типичные ошибки
- ✗Рассуждать об операндах как о математических целых, игнорируя преобразование
- ✗Считать, что беззнаковый операнд преобразуется к знаковому
- ✗Писать обратные циклы на
size_t, которые никогда не завершаются
Уточняющие вопросы
- →Каковы обычные арифметические преобразования для смешанных знаковых/беззнаковых операндов?
- →Почему
for (size_t i = n - 1; i >= 0; --i)зацикливается навсегда?
MiddleДизайнИногдаВ кодовой базе сырые int-идентификаторы вроде id пользователя и id заказа передаются повсюду и случайно путаются в местах вызова. Объясните, что такое strong typedef, чем он отличается от обычного typedef/alias и как его реализовать в C++, ведь родного синтаксиса для него в языке нет.
В кодовой базе сырые int-идентификаторы вроде id пользователя и id заказа передаются повсюду и случайно путаются в местах вызова. Объясните, что такое strong typedef, чем он отличается от обычного typedef/alias и как его реализовать в C++, ведь родного синтаксиса для него в языке нет.
typedef/alias даёт новое имя, но не новый тип: typedef int UserId; позволяет смешивать UserId и OrderId. Strong typedef оборачивает тип в отдельный класс: struct UserId { int v; }; (или boost::strong_typedef). В C++ нет родного синтаксиса — идиома через композицию.
Типичные ошибки
- ✗Считать
using UserId = int;типобезопасным — не так - ✗Забыть удалить нежелательные преобразования при обёртке
- ✗Добавить слишком много неявных преобразований и потерять безопасность
Уточняющие вопросы
- →Как
enum classдаёт типизацию для целых? - →Что предлагают
class enum/ opaque-type для будущего C++?
SeniorТеорияИногдаКаковы точные правила агрегатной инициализации в современном C++?
Каковы точные правила агрегатной инициализации в современном C++?
Агрегат — это массив или класс без объявленных пользователем конструкторов, без приватных нестатических членов-данных и без виртуальных функций. Brace-инициализация заполняет члены в порядке объявления; пропущенные хвостовые члены инициализируются значением. C++20 добавляет назначенные инициализаторы (.x = 1).
Типичные ошибки
- ✗Добавить один defaulted-конструктор в теле класса и невольно сломать статус агрегата (C++20)
- ✗Писать назначенные инициализаторы не в порядке объявления — некорректно в C++, в отличие от C
- ✗Забывать, что пропущенные члены инициализируются значением, а не остаются неопределёнными
Уточняющие вопросы
- →Почему C++17 изменил участие баз в агрегатной инициализации?
- →Как
std::arrayопирается на то, что является агрегатом?
SeniorДебаггингИногдаКогда именно ссылка становится висячей и как продление времени жизни это меняет?
Когда именно ссылка становится висячей и как продление времени жизни это меняет?
Ссылка становится висячей, когда объект, с которым она связана, уничтожается, пока ссылка ещё жива — её использование является неопределённым поведением. Привязка const T& к временному объекту продлевает его до области видимости ссылки. Но продление применяется только к прямой привязке: возврат ссылки её не продлевает.
Типичные ошибки
- ✗Возвращать
const T&на локальный или временный объект — продление не переживает возврат - ✗Хранить продлевающую жизнь ссылку в члене структуры и ждать, что временный объект сохранится
- ✗Привязать
const auto& x = vec.front();, затем менятьvec, и реаллокация инвалидируетx
Уточняющие вопросы
- →Почему привязка через параметр функции не продлевает время жизни временного объекта?
- →Чем висячие ссылки отличаются от висячих указателей по обнаружимости?
SeniorДебаггингИногдаЧто такое правило строгого алиасинга и как оно может вас укусить?
Что такое правило строгого алиасинга и как оно может вас укусить?
Правило строгого алиасинга гласит, что к хранимому значению можно обращаться только через lvalue его собственного типа, совместимого типа или char. Чтение float через int* — неопределённое поведение, поэтому оптимизатор считает, что такие указатели не пересекаются, и может переупорядочить или выбросить чтения. Безопасные инструменты — std::memcpy и std::bit_cast.
Типичные ошибки
- ✗Делать
reinterpret_castмежду несвязанными типами указателей и разыменовывать — классическое UB - ✗Считать поведение на
-O0доказательством корректности — алиасинг ломается лишь при оптимизации - ✗Полагать, что punning через
unionпереносим в C++ — он определён в C, но UB в C++
Уточняющие вопросы
- →Почему
std::bit_castстрого лучше punning черезmemcpyдля тривиальных типов? - →Как
char*получает особое исключение из правила алиасинга?
JuniorТеорияРедкоЧто такое ASCII и Unicode?
Что такое ASCII и Unicode?
ASCII — 7-битная кодировка, 128 символов. Unicode назначает кодовые точки каждому символу; UTF-8 — переменная 1–4 байта, совместим с ASCII, UTF-16 — 2 или 4 байта.
Типичные ошибки
- ✗Рассматривать длину
char*как количество символов — в UTF-8strlen()считает байты, а не кодовые точки Unicode или кластеры графем - ✗Применять
toupper()к байтам UTF-8 — работает только для ASCII; используйте ICU илиstd::localeдля преобразования регистра Unicode - ✗Считать, что одна кодовая точка = один видимый символ — составные диакритические знаки, последовательности эмодзи и кластеры графем — это многокодовые глифы
Уточняющие вопросы
- →Что такое BOM (Byte Order Mark) и когда он вызывает проблемы в файлах UTF-8?
- →Чем
std::wstring_convert(устарел в C++17) отличается отstd::codecvt?
JuniorТеорияРедкоКакие побитовые операции существуют в C++? Что такое булева алгебра?
Какие побитовые операции существуют в C++? Что такое булева алгебра?
Шесть побитовых: & (И), | (ИЛИ), ^ (XOR), ~ (НЕ), <</>> (сдвиги). Булева алгебра: тождества И/ИЛИ/НЕ (де Морган: ~(A & B) == ~A | ~B). Идиомы: установка x |= 1 << n, сброс x &= ~(1 << n), проверка (x >> n) & 1.
Типичные ошибки
- ✗Путать
&&(логическое И) с&(побитовое И) — тонкие баги при использовании с не-булевыми значениями - ✗Сдвигать знаковое целое влево так, что затрагивается знаковый бит — неопределённое поведение в C++ (используйте
unsignedдля битовых манипуляций) - ✗Применять правый сдвиг к знаковому отрицательному целому — поведение определяется реализацией (арифметический vs логический сдвиг)
Уточняющие вопросы
- →Как с помощью побитовых операций проверить, является ли целое число степенью двойки?
- →Что такое дополнение до двух и почему C++ опирается на него (гарантировано с C++20)?
SeniorТеорияРедкоЧто такое тривиальный тип и почему тривиальность важна для memcpy?
Что такое тривиальный тип и почему тривиальность важна для memcpy?
Тривиальный тип имеет предоставленные компилятором, невиртуальные специальные функции — никакой пользовательской логики при копировании или разрушении. Только тривиально копируемые типы можно memcpy: побайтовая копия даёт валидный объект, так как нет инвариантов для сохранения. memcpy для std::string — неопределённое поведение.
Типичные ошибки
- ✗Путать тривиальный со стандартной компоновкой — это независимые свойства, для
memcpyнужна тривиальная копируемость - ✗Делать
memcpyкласса с членомstd::string, ломая владение кучей и получая двойное освобождение - ✗Считать, что объявленная пользователем (даже
= default) специальная функция вне тела класса сохраняет тривиальность
Уточняющие вопросы
- →Чем
std::is_trivially_copyableотличается отstd::is_trivial? - →Почему
memcpyмежду разными тривиально копируемыми типами всё ещё может быть UB?