强类型枚举(enum class)¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++11 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-27 |
编译器支持:GCC 4.4+ · Clang 2.9+ · MSVC 17.0+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 用 enum class 表示文件类型,并在 switch 中使用限定名分支 | ev-mold-enum-class-filetype-001 |
| 用 enum class 区分 WebSocket 帧类型,避免与其它枚举名冲突 | ev-drogon-enum-class-ws-type-002 |
| 用 enum class 表示异步消息种类并驱动线程池分派 | ev-spdlog-enum-class-async-msg-003 |
本站证据页:ev-drogon-enum-class-ws-type-002 · ev-mold-enum-class-filetype-001 · ev-spdlog-enum-class-async-msg-003
解决什么问题¶
传统 enum 的枚举值直接进入外层作用域。两个库都定义 None 就会冲突;枚举值还能隐式转换成整数,和任意数字比较、参与算术运算,拼写错误也不会被发现。底层类型由实现选择,跨平台序列化时大小不确定;前向声明也不可行。
enum class 把枚举值关进自己的作用域,禁止隐式转换,并允许显式指定底层类型。要取值必须写 Type::value,要转成整数必须显式 static_cast。标准提案为 N2347。
基础用法¶
定义时在 enum 后写 class。使用枚举值要带类型名。
#include <iostream>
enum class Color : unsigned char { red = 1, green, blue };
enum class Status { ok = 200, not_found = 404 };
const char* name(Color color) {
switch (color) {
case Color::red:
return "red";
case Color::green:
return "green";
case Color::blue:
return "blue";
}
return "unknown";
}
int main() {
Color color = Color::green;
Status status = Status::not_found;
std::cout << static_cast<int>(color) << ' ' << name(color) << ' '
<< static_cast<int>(status) << '\n';
std::cout << (color == Color::green) << ' ' << (status == Status::ok) << '\n';
Color next = static_cast<Color>(static_cast<unsigned char>(color) + 1);
std::cout << name(next) << ' ' << sizeof(Color) << ' '
<< static_cast<int>(next) << '\n';
}
输出 2 green 404、1 0 与 blue 1 3。示例展示固定底层类型、switch 分支、作用域内比较和显式转换;Color 的底层类型是 unsigned char,因此 sizeof(Color) 为 1。完整版本见 examples/cpp11/enum_class.cpp。
进阶用法¶
底层类型写在枚举名之后:enum class Status : std::uint16_t { ... };。这固定了存储宽度和取值范围,适合网络协议、文件格式与跨语言 ABI。指定底层类型后还可以前向声明 enum class Status : std::uint16_t;,在不完整类型上定义指针或引用,减少头文件依赖。
enum class 不提供隐式算术和位运算。需要位标志时,要么在枚举外重载运算符,要么改用普通的无作用域枚举或专门的 flags 类型。不要为了省一次 static_cast 而退回无作用域枚举,类型安全的价值通常更大。
C++20 的 using enum Color; 可以把某个枚举的成员引入当前作用域,减少重复前缀;std::to_underlying(C++23)提供比 static_cast 更明确的整数转换。C++17 起,switch 覆盖不完整时主流编译器会给出警告,配合 -Wswitch 可以及早发现漏分支。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| mold | src/filetype.cc#L174-L182 |
用限定名做 switch 分支并比较文件类型 |
枚举值与解析逻辑绑定,避免整数魔法值 |
| drogon | lib/src/WebSocketConnectionImpl.cc#L45-L53 |
用 enum class 区分帧类型并映射到操作码 |
帧类型名称不会与其它枚举冲突 |
| spdlog | include/spdlog/details/thread_pool-inl.h#L102-L114 |
用 enum class 表示消息种类并驱动后台分派 |
消息类型需要限定作用域且分支完整 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三处都在生产路径上,覆盖类型判断、协议映射与任务分派三种用途。
陷阱与边界¶
没有隐式整数转换。 作为数组下标、传给 C 接口或做算术时都要显式 static_cast。这是有意的安全设计,不要用宏或全局常量绕过。
不提供位运算。 a | b 无法直接编译;需要组合标志时,显式重载运算符或改用整型位掩码。
底层类型默认不固定。 需要稳定大小或前向声明时,显式写 : std::uint8_t 等类型。
枚举值仍可被显式转换。 static_cast<Color>(99) 合法,可能得到一个没有名字的值;解析外部输入后要校验范围。
无作用域枚举并未废弃。 与 C 接口交互、需要隐式整数转换或位标志时,普通 enum 仍可能是合适选择;关键是明确取舍。
序列化要固定数值。 依赖枚举顺序会在插入新值时改变已有取值;对外协议应显式写出每个枚举值。
库联动¶
- 标准库:
std::byte、std::memory_order、std::launch、std::future_status等都用强类型枚举表达离散状态。 - 容器与算法:枚举可作为
std::map的键,也可以配合std::underlying_type取得底层类型。 - 网络与协议库:drogon 用强类型枚举区分 WebSocket 帧类型;spdlog 用它表示异步消息种类。
- 链接器与工具链:mold 用强类型枚举表示文件类型,避免不同格式的整数常量互相污染。
横向对比¶
| 方案 | 名称作用域 | 隐式转整数 | 可指定底层类型 | 主要代价 |
|---|---|---|---|---|
无作用域 enum |
外层 | 是 | 是 | 名称冲突,类型不安全 |
enum class |
枚举内 | 否 | 是 | 每次转换都要显式 static_cast |
constexpr 常量 |
所在作用域 | 是 | 不适用 | 没有独立类型,无法整体遍历 |
| 类内静态常量 | 类内 | 视类型而定 | 不适用 | 样板代码多,不能约束取值集合 |
跨标准演进:C++11 通过 N2347 引入强类型枚举;C++17 允许用花括号初始化枚举;C++20 增加 using enum,C++23 提供 std::to_underlying。