Перечисления и switch в C++: что нужно знать до начала
Разберём, чем enum class отличается от старого enum, почему switch по строке не работает и как компилятор помогает не забыть ветку. С примерами под C++17 и новее.

За полчаса вы научитесь объявлять перечисления двумя способами, безопасно перебирать их значения в switch и понимать, почему компилятор ругается на enum class там, где старый enum молчал. Всё, что нужно заранее — компилятор с поддержкой C++17 (GCC 9+, Clang 10+, MSVC 2019+) и умение собрать один файл через g++ main.cpp -std=c++17 или в любой IDE.
Что нужно иметь до начала
- компилятор с C++17: проверьте версию командой
g++ --versionилиclang++ --version; - флаг стандарта при сборке:
-std=c++17для GCC/Clang, в MSVC —/std:c++17; - флаги предупреждений
-Wall -Wextra— без них компилятор промолчит о забытой ветке вswitch, а именно она чаще всего ломает логику.
Дальше — по шагам. Каждый фрагмент компилируется целиком, без «остального по аналогии».
Два вида перечислений
В C++ есть два способа объявить перечисление: старый (unscoped, ещё из C) и новый — enum class (scoped, с C++11). Разница не косметическая, и от выбора зависит, сколько ошибок компилятор поймает за вас.
Старый enum: удобно, но опасно
#include <iostream>
enum Color { Red, Green, Blue };
int main() {
Color c = Green;
int number = c; // молча превращается в 1
std::cout << number << "\n";
return 0;
}Здесь Red, Green, Blue попадают в тот же уровень имён, что и всё вокруг. Если рядом объявить enum Fruit { Apple, Green }; — получите ошибку переопределения Green. И любое значение молча приводится к int. Это удобно ровно до первого бага, когда цвет случайно сложился с числом.
enum class: строже и без сюрпризов
#include <iostream>
enum class Color { Red, Green, Blue };
int main() {
Color c = Color::Green;
// int number = c; // не скомпилируется: нет неявного приведения
int number = static_cast<int>(c); // нужно явно
std::cout << number << "\n"; // выведет 1
return 0;
}Теперь имена живут внутри Color — обращаетесь только через Color::Green. Неявного приведения к числу нет: чтобы получить int, пишете static_cast явно. Два разных перечисления с одинаковым именем константы больше не конфликтуют.
Правило по умолчанию для новичка простое: пишитеenum class, пока не появилась конкретная причина использовать старыйenum. Строгость компилятора здесь работает на вас.
Задать тип и значения вручную
По умолчанию первое значение — 0, каждое следующее на единицу больше. Можно задать и явные числа, и базовый целый тип:
#include <cstdint>
enum class HttpStatus : std::uint16_t {
Ok = 200,
NotFound = 404,
ServerError = 500
};Запись : std::uint16_t фиксирует, что значения хранятся в 16-битном беззнаковом типе. Это полезно, когда перечисление кладётся в структуру фиксированного размера или сериализуется в бинарный формат.
switch по перечислению
Именно ради switch перечисления чаще всего и заводят. Компилятор умеет проверять, что вы обработали все значения — но только если правильно попросить.
#include <iostream>
enum class Direction { North, East, South, West };
const char* toArrow(Direction d) {
switch (d) {
case Direction::North: return "вверх";
case Direction::East: return "вправо";
case Direction::South: return "вниз";
case Direction::West: return "влево";
}
return "неизвестно"; // на случай мусорного значения из cast
}
int main() {
std::cout << toArrow(Direction::East) << "\n"; // вправо
return 0;
}Проверка: соберите файл и запустите — должно вывести вправо. Обратите внимание, что default здесь нет намеренно, об этом ниже.
Почему нет break
В ветках выше стоит return, поэтому break не нужен — управление уходит из функции. Но если ветка не завершается return и вы забыли break, начнётся «проваливание» (fall-through): выполнение потечёт в следующую ветку. Это классический источник ошибок:
switch (d) {
case Direction::North:
std::cout << "север\n";
// забыли break — провалимся в East
case Direction::East:
std::cout << "восток\n";
break;
}При North этот код напечатает обе строки. Если проваливание задумано специально, начиная с C++17 помечайте это атрибутом [[fallthrough]]; — тогда компилятор поймёт, что так и надо, и не выдаст предупреждение.
Как заставить компилятор ловить забытые ветки
Соберите с -Wall -Wextra (в частности сюда входит -Wswitch). Если в switch по enum class нет ветки на какое-то значение и нет default, компилятор предупредит:
warning: enumeration value 'West' not handled in switch [-Wswitch]Отсюда неочевидный вывод: default в switch по перечислению — не всегда добро. Пока default есть, компилятор считает все значения обработанными и молчит. Уберёте default — и при добавлении нового значения в enum он напомнит про каждый switch, который пора обновить. Это дешёвая страховка от «забыли обработать новый статус».
Когда какой enum выбирать
| Ситуация | Что брать | Почему |
|---|---|---|
| Новый код, флаги состояний, статусы | enum class | нет неявных приведений, имена не конфликтуют |
| Нужно часто использовать значение как индекс массива | enum class + явный static_cast<int> | приведение видно в коде, ошибки заметнее |
| Совместимость с сишным API или готовыми заголовками | старый enum | C не знает enum class, автоприведение к int ожидаемо |
| Битовые флаги (OR/AND значений) | старый enum или enum class с перегрузкой операторов | у enum class нет готовых битовых операций — их надо описать вручную |
Если не получилось
error: 'Green' was not declared in this scope— вы обращаетесь к значениюenum classбез имени перечисления. ПишитеColor::Green, а неGreen.error: cannot convert 'Color' to 'int'— пытаетесь использоватьenum classкак число напрямую. Добавьтеstatic_cast<int>(c).error: 'enum class' does not name a type(или отказ отenum class) — компилятор собирает в старом стандарте. Добавьте флаг-std=c++17(или/std:c++17в MSVC).- Предупреждение
-Wswitchне появляется — либо не заданы-Wall -Wextra, либо вswitchестьdefault, который глушит проверку. Уберитеdefault, если хотите контроль полноты. - switch печатает лишнюю строку — забыли
breakв ветке безreturn, идёт проваливание. Добавьтеbreakили, если проваливание намеренное,[[fallthrough]];.
Что дальше
Когда базовый switch работает, посмотрите в сторону трёх вещей. Первое — вывод перечисления в текст: удобный способ появился в C++23 через <print>, но пока проще писать свою функцию toString, как toArrow выше. Второе — std::underlying_type_t<Enum> из заголовка <type_traits>, чтобы узнать базовый тип перечисления в шаблонном коде. Третье — рефлексия перечислений (получить имена значений автоматически): в стандарте её пока нет, до неё обычно берут внешние библиотеки, но это отдельная тема. Начните с привычки писать enum class и собирать с -Wall -Wextra — это закроет большинство ошибок ещё на этапе компиляции.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Enumerations — cppreference.com, switch statement — cppreference.com
Частые вопросы
А если у меня только C++11 или C++14, а не C++17?
Почему switch не работает со строками?
Обязательно ли писать default в switch по enum?
Как перебрать все значения enum в цикле?
Занимает ли enum class больше памяти, чем обычный enum?
Что выбрать для битовых флагов — enum или enum class?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.