Перечисления и switch в C++: частые ошибки новичков
Компилятор молчит, а enum ведёт себя не так, как ждёте: значения перепутаны, switch проваливается сквозь case, а сравнение с int неожиданно проходит. Разбираем частые грабли на C++11 и новее.

Вы объявили enum, написали switch по нему, всё скомпилировалось — а программа выдаёт не тот результат. Знакомая ситуация. C++ разрешает многое, что логичнее было бы запретить: старые нескоуповые перечисления неявно превращаются в int, switch без break проваливается в следующий case, а забытый вариант перечисления компилятор пропускает молча. Ниже — разбор ошибок, на которых спотыкаются почти все, кто только начал работать с enum и switch. Примеры проверены на C++17, ключевые особенности помечены версией.
Что понадобится: любой компилятор с поддержкой C++11 и выше (GCC 7+, Clang 6+, MSVC 2017+) и флаги предупреждений. Флаги — не роскошь: половина описанных ошибок ловится компилятором, если попросить его ругаться. Для GCC и Clang это -Wall -Wextra, для MSVC — /W4.
Ошибка 1. Нескоуповый enum течёт в int
Классический enum без слова class — это фактически именованный набор целых, который свободно смешивается с числами.
// C++11 и новее
enum Color { Red, Green, Blue }; // Red=0, Green=1, Blue=2
Color c = Green;
int sum = c + 10; // компилируется: c неявно стал int, sum == 11
bool wrong = (c == 1); // тоже компилируется и даёт true
Проблема не в том, что это не работает, а в том, что это работает молча. Вы можете сравнить Color с Size, сложить два разных перечисления, передать Color в функцию, которая ждёт int — и компилятор ничего не скажет. Логическая ошибка всплывёт в рантайме.
Решение появилось в C++11 — scoped enum (перечисление с областью видимости):
// C++11 и новее
enum class Color { Red, Green, Blue };
Color c = Color::Green; // имя обязательно через Color::
// int sum = c + 10; // ОШИБКА компиляции — и это хорошо
int value = static_cast<int>(c); // явное преобразование, если оно правда нужно
С enum class имена не засоряют внешнюю область видимости и нет неявного превращения в число. Если вам не нужна совместимость со старым C-кодом, по умолчанию используйте enum class.
Старыйenumудобен ровно до первого бага, когда два разных перечисления случайно сложились и дали правдоподобное, но неверное число.enum classломает такую сборку на этапе компиляции — это дешевле, чем ловить её в проде.
Ошибка 2. Забытый break — проваливание сквозь case
В C++ каждый case без break продолжает выполнять код следующего. Это не баг языка, а его правило, унаследованное от C, — но новичка оно ловит регулярно.
enum class State { Idle, Running, Done };
void handle(State s) {
switch (s) {
case State::Idle:
std::cout << "idle\n";
// break забыт — управление провалится в Running
case State::Running:
std::cout << "running\n";
break;
case State::Done:
std::cout << "done\n";
break;
}
}
При handle(State::Idle) выведется и idle, и running. Компилятор по умолчанию молчит.
Что делать:
- Ставьте
breakв конце каждогоcaseи приучите себя писать его сразу, а не «потом». - Если проваливание нужно осознанно — с C++17 есть атрибут
[[fallthrough]];. Он документирует намерение и убирает предупреждение. - Включите предупреждение о неявном проваливании: в GCC и Clang это
-Wimplicit-fallthrough.
// C++17: осознанное проваливание
switch (s) {
case State::Idle:
prepare();
[[fallthrough]]; // явно: да, идём дальше
case State::Running:
run();
break;
}
Ошибка 3. switch не покрывает все значения enum
Вы добавили новое значение в перечисление, но забыли обработать его в одном из switch. Компилятор об этом не напомнит — если вы его не попросите.
Флаг -Wswitch (входит в -Wall у GCC и Clang) выдаёт предупреждение, когда в switch по enum не перечислены все значения и нет default. Ключевая тонкость: как только вы добавляете default, это предупреждение исчезает — компилятор считает, что вы обработали остальные случаи.
enum class State { Idle, Running, Done };
// Без default — GCC/Clang с -Wall предупредят о непокрытом State::Done
switch (s) {
case State::Idle: /* ... */ break;
case State::Running: /* ... */ break;
}
Вывод практический: если хотите, чтобы компилятор ловил забытые значения при расширении enum, не добавляйте default без нужды. Тогда каждое новое значение будет подсвечено предупреждением во всех незакрытых switch.
Ошибка 4. Опора на автоматические значения enum
Значения перечисления по умолчанию начинаются с нуля и растут на единицу. Проблемы начинаются, когда вы задаёте часть значений вручную:
enum class Code {
Ok = 200,
NotFound = 404,
Teapot, // == 405, не 404 и не 0
ServerError = 500
};
Teapot получит 405 — следующее за NotFound. Если вы рассчитывали на другое число или на автонумерацию с нуля, будет тихая ошибка. Задавайте значения явно, когда они что-то означают (коды ответа, битовые флаги, идентификаторы протокола), и не полагайтесь на порядок.
Ошибка 5. Приведение произвольного int к enum
Обратное преобразование static_cast<State>(x) компилятор разрешает для любого int, даже если такого значения в перечислении нет.
enum class State { Idle, Running, Done }; // 0, 1, 2
State s = static_cast<State>(99); // компилируется, но такого State нет
Дальше switch (s) не попадёт ни в один case, и без default вы получите тихое ничего. Если данные приходят извне (из сети, файла, пользовательского ввода), проверяйте диапазон явно перед приведением, а в switch по внешним данным default как раз уместен — для отсева мусора.
Задать тип хранения enum
С C++11 можно указать целочисленный тип, в котором хранится перечисление. Это важно для сериализации и экономии памяти:
// C++11 и новее
enum class ErrorCode : std::uint16_t {
None = 0,
Timeout = 1,
Refused = 2
};
Без явного указания тип нескоупового enum определяется реализацией, а у enum class по умолчанию это int. Если вы пишете значение в бинарный формат или в сеть, зафиксируйте тип — иначе размер поля может отличаться между платформами.
Что где выбрать
| Ситуация | Что использовать | Почему |
|---|---|---|
| Новый код, типобезопасность важна | enum class | Нет неявного превращения в int, имена в своей области |
| Совместимость со старым C-API | нескоуповый enum | Неявное преобразование к int иногда нужно |
| Битовые флаги | enum class + перегрузка операторов или enum | У scoped enum операторы `|`/`&` надо определять вручную |
| switch по данным из сети/файла | любой enum + default | Внешнее значение может не совпасть ни с одним case |
| switch по своему закрытому набору состояний | любой enum, без default | Компилятор подсветит непокрытые значения |
Если не собирается или ведёт себя странно
- «error: 'Green' was not declared in this scope» — вы используете
enum class, но обращаетесь к значению без префикса. ПишитеColor::Green. - «error: no match for 'operator+' (operand types are 'Color' and 'int')» —
enum classне превращается в число сам. Если преобразование правда нужно, используйтеstatic_cast<int>(c). - «warning: enumeration value 'Done' not handled in switch» — в
switchне хватаетcaseдля одного из значений. Добавьте его или, если так задумано,default. - «warning: this statement may fall through» (при
-Wimplicit-fallthrough) — забытbreak. Добавьте его либо[[fallthrough]];, если проваливание намеренное. - Программа заходит не в ту ветку
switch— проверьте явно заданные значения enum (Ошибка 4) и не приводится ли к enum число вне диапазона (Ошибка 5).
Что настроить дальше
Первым делом включите предупреждения в сборке: -Wall -Wextra -Wimplicit-fallthrough для GCC/Clang или /W4 для MSVC. Большая часть описанных ошибок ловится именно ими. Дальше посмотрите, как в вашем проекте организована сериализация перечислений и есть ли централизованная функция «int → enum» с проверкой диапазона — если каждый модуль кастует сам, рано или поздно кто-то передаст мусор. Для битовых флагов на enum class посмотрите в сторону аккуратной перегрузки операторов вместо возврата к небезопасному нескоуповому enum.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: cppreference: enum declaration, cppreference: switch statement
Частые вопросы
У меня старый компилятор без C++11, работает enum class?
Почему компилятор не предупреждает о забытом break?
Можно ли делать switch по enum class без static_cast?
Мой switch не заходит ни в один case, хотя значение вроде правильное. В чём дело?
Что будет, если добавить [[fallthrough]] в C++11 или C++14?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.