Item10:优先选用限域枚举,而非不限域枚举
使用限域enum来减少命名空间污染
通用规则:如果在一对大括号里声明一个名字,则该名字的可见性限定在括号括起来的作用域内。
但这个规则不适用于 C++98 风格的枚举类型中定义的枚举变量:枚举变量的属于包含着这个枚举类型的作用域,此作用域内不能有其他实体取相同的名字。
1 | enum Color { black, white, red }; //black, white, red在Color所在的作用域 |
枚举量的名字泄露到枚举类型所在作用域的现象被称为:不限范围的枚举类型(未限域 enum)。
在 C++11 中,限定作用域的枚举类型(枚举类 / 限域 enum)不会以这样的方式泄露名字:
1 | enum class Color { black, white, red }; //black, white, red限制在Color域内 |
限域枚举的枚举量是强类型的
强类型:语言会对类型兼容性做严格检查,禁止隐式的、不安全的类型转换,每个值都有明确且固定的类型,类型错误会被尽可能早地捕获(编译器 / 运行期)。
不限域枚举类型的枚举量可以隐式转换为整数类型(并能够从此处进一步转换到浮点类型),导致语义扭曲的代码合法:
1 | enum Color { black, white, red }; //未限域enum |
从限定作用域的枚举类型到任何其他类型都不存在隐式转换路径,类型安全更优:
1 | enum class Color { black, white, red }; //Color现在是限域enum |
限定作用域的枚举类型可以进行前置声明
前置声明的核心前提是编译器能确定枚举的底层类型(内存大小):
1 | enum Color; // 错误!未限域enum无默认底层类型,编译器无法推断内存大小 |
底层类型与前置声明的完整规则
- 所有 enum 的底层类型是编译器为优化内存选择的整型(需覆盖所有枚举值的最小类型);
- 限域 enum:默认底层类型为
int(也可显式指定),因此总能直接前置声明:
1 | enum class Status; // 默认int,合法 |
- 未限域 enum:无默认底层类型,仅显式指定底层类型时才能前置声明:
1 | enum Color: std::uint8_t; // 显式指定底层类型,合法 |
- 两者均可在定义时显式指定底层类型:
1 | enum class Status: std::uint32_t { good = 0, failed = 1 }; |
未限域枚举的唯一实用场景:简化 std::tuple 字段访问
限域 enum 虽类型安全,但访问std::tuple字段时因无隐式转换,代码冗长;未限域 enum 可利用 “隐式转换为 std::size_t” 简化模板实参传递:
1 | // 定义tuple类型(存储用户名、邮箱、声望值) |
限域枚举访问 tuple 的优化方案:toUType constexpr 函数模板
为兼顾限域 enum 的类型安全与 tuple 访问的简洁性,可实现编译期生效的toUType函数模板(转换枚举值为其底层类型):
C++11 版本:
1 | template<typename E> |
C++14 简化版本(更简洁):
1 | template<typename E> |
调用方式(兼顾安全与简洁):
1 | auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo); |
总结
- C++98风格的枚举类型,现在称为不限范围的枚举类型。
- 限定作用域的枚举类型仅在枚举类型内可见。它们只能通过强制类型转换以转换至其他类型。
- 限定作用域的枚举类型和不限范围的枚举类型都支持底层类型指定。限定作用域的枚举类型的默认底层类型是
int,而不限范围的枚举类型没有默认底层类型。 - 限定作用域的枚举类型总是可以进行前置声明,而不限范围的枚举类型却只有在指定了默认底层类型的前提下才可以进行前置声明。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论
