💡 Кратко:Учебник по enum в C++: классические перечисления, enum class из C++11, enum внутри классов и как рефлексия C++26 (std::meta) наконец решает преобразование enum в строку без макросов и таблиц поиска.
Последнее обновление:2026-10-24
C++ перечисления enum¶
Об авторе:¶
Личный официальный аккаунт:

Традиционное поведение¶
У перечислений есть следующие проблемы:
- Неограниченная область видимости легко приводит к конфликтам имён. Например, следующий код не компилируется:
enum Color
{
RED,
BLUE
};
enum Feeling
{
EXCITED,
BLUE
};
int main()
{
Color a = BLUE; // error
Feeling b = EXCITED;
std::cout << a << ":" << b << std::endl;
return 0;
}
Конкретная реализация: tradition_color.cpp
Классический подход¶
Простой способ решить проблему конфликтов имён, вызванную неограниченной областью видимости, — добавлять префикс к именам перечислителей: например, в примере выше заменить на COLOR_BLUE и FEELING_BLUE.
Как правило, ради единообразия добавляют префикс ко всем константам. Но код определения таких переменных перечисления выглядит громоздко. В программах на C, возможно, не обойтись без этого, однако программистам C++ такой способ вряд ли понравится. Альтернатива — пространства имён:
После этого можно определять новые переменные перечисления так: Color::Type c = Color::RED;. Если сделать using namespace Color, префикс можно вообще опустить, что упрощает код. Однако пространство имён впоследствии может пополняться новым содержимым, поэтому обеспечиваемая им закрытость области видимости невысока. В больших проектах всё же возможно, что разные люди дадут одинаковое имя разным перечислимым типам.
Более «эффективный» способ — ограничить область видимости классом или структурой; например: способ определения новых переменных такой же, как с пространством имён выше, но при этом не нужно беспокоиться, что содержимое класса изменят где-то ещё. Здесь используется структура, а не класс, потому что сами константы задуманы как общедоступные.
Конкретная реализация: classic_practice.cpp
Перечисления-классы C++11¶
Описанный выше подход решает первую проблему, но бессилен перед двумя остальными. К счастью, в стандарт C++11 были введены «перечисления-классы» (enum class), которые неплохо решают упомянутые проблемы.
- Область видимости нового enum больше не глобальная
- Не может неявно преобразовываться в другие типы
/**
* @brief C++11的枚举类
* 下面等价于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
- Можно указать конкретный тип для хранения enum
enum class Color3:char; // 前向声明
// 定义
enum class Color3:char
{
RED='r',
BLUE
};
char c3 = static_cast<char>(Color3::RED);
Конкретная реализация: classic_practice.cpp
Перечислимые типы в классе¶
Иногда мы хотим, чтобы определённые константы были действительны только внутри класса. Поскольку макроконстанты, определённые через #define, глобальны и не достигают этой цели, напрашивается идея использовать члены данных, модифицированные const. Однако члены данных const действительно существуют, но их смысл не тот, которого мы ожидаем.
Член данных const константен лишь в течение времени жизни конкретного объекта, а для всего класса он изменяем, поскольку класс может создавать множество объектов, и значения члена данных const в разных объектах могут различаться.
Нельзя инициализировать член данных const в объявлении класса. Следующее применение ошибочно, поскольку до создания объекта класса компилятор не знает, чему равен SIZE. (до стандарта C++11)
Правильно делать это в списке инициализации конструктора класса:
class A
{
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的定义
{
}
A a(100); // 对象 a 的 SIZE 值为 100
A b(200); // 对象 b 的 SIZE 值为 200
Как же создать константу, постоянную в рамках всего класса?
Не рассчитывайте на члены данных const — следует использовать перечислимые константы в классе. Например:
class Person{
public:
typedef enum {
BOY = 0,
GIRL
}SexType;
};
//访问的时候通过,Person::BOY或者Person::GIRL来进行访问。
Перечислимые константы не занимают памяти в объекте; они полностью вычисляются во время компиляции.
Недостаток перечислимых констант: их неявный тип данных — целочисленный, их максимальное значение ограничено, и они не могут представлять числа с плавающей точкой.
enum в строку: ответ рефлексии C++26¶
Превратить значение enum в его имя — задача, на которую наталкивается почти каждый проект на C++, и до C++26 в стандартной библиотеке не было ответа. Обходные пути, от старых к новым: (1) X-макросы, злоупотребляющие препроцессором, чтобы держать список перечислителей в одном месте; (2) рукописные таблицы поиска или специализация шаблонов — каждый новый enum означает ещё одну таблицу; (3) сторонние библиотеки вроде magic_enum, которые «выуживают» имена трюками компилятора, но ограничивают размер и базовый тип.
Статическая рефлексия C++26 (предложение P2996, std::meta) наконец превращает это в несколько строк настоящего кода. Одна функция перечисляет члены любого enum на этапе компиляции и читает имя каждого перечислителя:
#include <meta>
template <typename E>
requires std::is_enum_v<E>
std::string enum_to_string(E value) {
template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
if (value == [:e:]) {
return std::meta::identifier_of(e);
}
}
throw std::invalid_argument("unknown enumerator");
}
Теперь одна функция покрывает все enum в кодовой базе. Переименование перечислителя автоматически обновляет строку, а несовпадение выявляется при компиляции, а не неверной меткой в рантайме.
Честная оговорка: фрагмент следует P2996 в том виде, в каком он вошёл в черновик C++26. Точные написания ещё менялись в ранних превью компиляторов (EDG и Clang explorers), поэтому сверьтесь с таблицей поддержки вашего инструментария. До тех пор таблица поиска остаётся самым переносимым решением без зависимостей.
Часто задаваемые вопросы¶
Как преобразовать enum в строку в C++?¶
До C++26 стандартного способа нет: выбор между таблицей поиска, специализацией шаблонов, X-макросами или библиотекой вроде magic_enum. Статическая рефлексия C++26 перечисляет члены на компиляции и читает имя напрямую, устраняя сопровождение всех четырёх вариантов.
Есть ли в стандартной библиотеке встроенное преобразование enum в строку?¶
До C++26 — нет. Потоки и std::to_string форматируют только целочисленное значение, поэтому каждый проект в итоге пишет собственный помощник.
Когда реально можно пользоваться рефлексией C++26?¶
C++26 — грядущий стандарт, рефлексия уже в черновике; EDG и Clang explorers компилируют примеры. Для продакшена отслеживайте поддержку компилятора и держите таблицу поиска как запасной вариант.
Похожие темы¶
- C++ чисто виртуальные функции и абстрактные классы | C++那些事
- C++ утверждения assert | C++那些事
- C++ битовые поля | C++那些事
- Реализация полиморфизма C++ на языке C | C++那些事
- C++ const подробно: константы, указатель на const и const-указатель | C++那些事
- C++ всё семейство const | C++那些事
- C++ оператор разыменования * | C++那些事
- C++ инициализация и присваивание | C++那些事
- C++ возврат нескольких значений | C++那些事
- C++ ключевое слово mutable | C++那些事