枚举的直接列表初始化 - 对 enum class 的改进

1. 枚举初始化

在 C++17 之前,给作用域枚举(强类型枚举)变量赋值有时会很麻烦,甚至不得不写强转。C++17 修复了这个痛点,让枚举的使用变得更像普通的整数变量一样自然。

如果我们定义了一个作用域枚举(C++11),想要给它初始化一个值,必须小心翼翼。假设我们有一个代表颜色的枚举:

1
2
3
4
5
6
enum class Color : unsigned int 
{
Red = 1,
Green = 2,
Blue = 3
};

如果我们想在代码中临时定义一个灰度值(比如 0),这样做是不行的:

1
2
// 错误!C++17 之前不允许用整数直接初始化 enum class
Color c = 0; // 编译报错!类型不匹配

以前必须显式地告诉编译器:“我知道我在干什么,请把这个整数强转成 Color。”

1
2
// 麻烦的旧做法:必须写 static_cast
Color c = static_cast<Color>(0); // 即使是0,也得写这么长一串

这种写法非常繁琐,尤其是在处理底层硬件寄存器、网络协议或者位掩码时,枚举可能代表很多未在枚举定义中列举出的数值,每次都写 static_cast 简直是灾难。

C++17 引入了一个新规则:允许使用花括号初始化列表 {},直接将整数(或其他类型的数值)赋值给枚举变量。只要这个数值在枚举底层类型的范围内(比如 int 范围内),就可以直接用 {} 赋值

1
2
3
// 使用花括号,直接初始化!不需要 static_cast
Color c1 {0}; // 编译通过!
Color c2 {255}; // 编译通过!只要在 unsigned int 范围内
  • 这种写法只适用于花括号 {} 初始化。
  • 如果直接用等号 =、圆括号或独立的花括号来赋值,编译器依然会报错(除非你用了强转)。
1
2
3
Color c3 = 0;     // error
Color c4 = {0}; // error
Color c5(0); // error

2. 示例代码

为了让大家全面理解,下面准备了一段完整的代码示例,涵盖了普通枚举、作用域枚举(强类型枚举)以及边界情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <string>

// 1. 普通枚举
enum OldEnum
{
A = 10,
B = 20
};

// 2. 作用域枚举,指定底层类型为 unsigned char
// 这种情况在底层硬件开发中很常见
enum class Status : unsigned char
{
OK = 0,
Error = 1,
Unknown = 99
};

// 3. 另一个枚举,底层类型是 int
enum class Permission : int
{
Read = 1,
Write = 2,
Exec = 4
};

int main()
{
std::cout << "=== C++17 枚举直接列表初始化测试 ===" << std::endl;

// --- 场景 A:普通枚举 ---
std::cout << "\n[普通枚举测试]" << std::endl;
// 经测试 g++、msvc 都不支持 C++17 的 {} 语法
// OldEnum e1{ 10 };

// --- 场景 B:enum class (重点来了) ---
std::cout << "\n[enum class 测试]" << std::endl;

// 旧做法 C++17 之前:
// Status s1 = static_cast<Status>(50); // 烦死了

// 新做法 C++17:
Status s1{ 50 }; // ✅ 完美!直接用大括号赋予一个未定义的值
Status s2 = { Status::OK }; // ✅ 当然也可以用已有的枚举值
Status s3{ 255 }; // ✅ 只要不超过 unsigned char (0-255) 的范围

std::cout << "Status s1 的原始值: " << static_cast<int>(s1) << std::endl;
std::cout << "Status s2 的原始值: " << static_cast<int>(s2) << std::endl;
std::cout << "Status s3 的原始值: " << static_cast<int>(s3) << std::endl;


// --- 场景 C:错误的示范 ---
// 1. 忘记用大括号,直接用等号
// Status bad1 = 100; // ❌ 编译错误:不能将 'int' 转换为 'Status'

// 2. 用圆括号构造
// Status bad2(100); // ❌ 编译错误:没有匹配的构造函数

// 3. 超出底层类型的范围
// Status bad3 {999}; // ❌ 编译错误:从 'int' 转换到 'unsigned char' 缩窄了范围
// 解释:999 存不进 unsigned char,C++ 列表初始化为了安全,禁止这种缩窄转换。


// --- 场景 D:实际应用 - 位掩码组合 ---
std::cout << "\n[实际应用:组合权限]" << std::endl;
Permission p{ 1 | 2 };
std::cout << "组合权限 p 的值: " << static_cast<int>(p) << std::endl;

return 0;
}

为了帮大家在面试或使用中记住enum class 这个特性,请记住以下三大要点:

  1. 必须用大括号 {}

    只有 Color c { value }; 是对的。Color c (value);Color c = value;enum class 下依然被禁止。

  2. 底层类型要匹配:给的值必须能塞进枚举的底层类型里。

    如果是 enum class E : char,你不能给它赋值 999,因为 char 装不下。这种“缩窄”是不允许的。

  3. 不需要在 enum 列表里

    {} 里的数字不一定要出现在枚举定义 { A, B... } 中。这对处理协议里的保留位未来扩展值非常有用。