【Effective Modern C++】第三章 转向现代C++:10. 优先选用限域枚举,而非不限域枚举

使用限域enum来减少命名空间污染

通用规则:如果在一对大括号里声明一个名字,则该名字的可见性限定在括号括起来的作用域内。

但这个规则不适用于 C++98 风格的枚举类型中定义的枚举变量:枚举变量的属于包含着这个枚举类型的作用域,此作用域内不能有其他实体取相同的名字。

c++ 复制代码
enum Color { black, white, red }; //black, white, red在Color所在的作用域
auto white = false; //错误! white早已在这个作用域中声明

枚举量的名字泄露到枚举类型所在作用域的现象被称为:不限范围的枚举类型(未限域 enum)。

在 C++11 中,限定作用域的枚举类型(枚举类 / 限域 enum)不会以这样的方式泄露名字:

c++ 复制代码
enum class Color { black, white, red }; //black, white, red限制在Color域内
auto white = false; //没问题,域内没有其他"white"

Color c = white; //错误,域中没有枚举名叫white
Color c = Color::white; //没问题
auto c = Color::white; //也没问题(也符合5的建议)

限域枚举的枚举量是强类型的

强类型:语言会对类型兼容性做严格检查,禁止隐式的、不安全的类型转换,每个值都有明确且固定的类型,类型错误会被尽可能早地捕获(编译器 / 运行期)。

不限域枚举类型的枚举量可以隐式转换为整数类型(并能够从此处进一步转换到浮点类型),导致语义扭曲的代码合法:

c++ 复制代码
enum Color { black, white, red }; //未限域enum

std::vector<std::size_t>primeFactors(std::size_t x); //func返回x的质因子

Color c = red;
if (c < 14.5) { // Color与double比较 (!)
    auto factors = primeFactors(c); // 向std::size_t参数传递Color (!)
    ...
}

从限定作用域的枚举类型到任何其他类型都不存在隐式转换路径,类型安全更优:

c++ 复制代码
enum class Color { black, white, red }; //Color现在是限域enum

Color c = Color::red;                   
if (c < 14.5) {                         //错误!不能比较Color和double
    auto factors = primeFactors(c);     //错误!不能向std::size_t参数传Color
    ...
}
// 仅显式强制转换才允许(可控的类型转换)
if (static_cast<double>(c) < 14.5) {
    auto factors = primeFactors(static_cast<std::size_t>(c));
}

限定作用域的枚举类型可以进行前置声明

前置声明的核心前提是编译器能确定枚举的底层类型(内存大小):

c++ 复制代码
enum Color; // 错误!未限域enum无默认底层类型,编译器无法推断内存大小
enum class Color; // 没问题!限域enum默认底层类型为int,内存大小确定

底层类型与前置声明的完整规则

  1. 所有 enum 的底层类型是编译器为优化内存选择的整型(需覆盖所有枚举值的最小类型);
  2. 限域 enum:默认底层类型为int(也可显式指定),因此总能直接前置声明:
c++ 复制代码
    enum class Status; // 默认int,合法
    enum class Status: std::uint32_t; // 显式指定底层类型,合法
  1. 未限域 enum:无默认底层类型,仅显式指定底层类型时才能前置声明:
c++ 复制代码
    enum Color: std::uint8_t; // 显式指定底层类型,合法
  1. 两者均可在定义时显式指定底层类型:
c++ 复制代码
    enum class Status: std::uint32_t { good = 0, failed = 1 };
    enum Color: std::uint8_t { black, white, red };

未限域枚举的唯一实用场景:简化 std::tuple 字段访问

限域 enum 虽类型安全,但访问std::tuple字段时因无隐式转换,代码冗长;未限域 enum 可利用 "隐式转换为 std::size_t" 简化模板实参传递:

c++ 复制代码
// 定义tuple类型(存储用户名、邮箱、声望值)
using UserInfo = std::tuple<std::string, std::string, std::size_t>;

// 未限域enum:简洁访问tuple字段(依赖隐式转换为std::size_t)
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
auto val = std::get<uiEmail>(uInfo); // 直接用枚举成员作为模板实参

// 限域enum:无隐式转换,代码冗长
enum class UserInfoFields { uiName, uiEmail, uiReputation };
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);

限域枚举访问 tuple 的优化方案:toUType constexpr 函数模板

为兼顾限域 enum 的类型安全与 tuple 访问的简洁性,可实现编译期生效 的toUType函数模板(转换枚举值为其底层类型):

C++11 版本:

c++ 复制代码
template<typename E>
constexpr typename std::underlying_type<E>::type
    toUType(E enumerator) noexcept
{
    return static_cast<typename std::underlying_type<E>::type>(enumerator);
}

C++14 简化版本(更简洁):

c++ 复制代码
template<typename E>
constexpr auto toUType(E enumerator) noexcept
{
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

调用方式(兼顾安全与简洁):

c++ 复制代码
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

总结

  • C++98风格的枚举类型称为不限范围的枚举类型。
  • 限定作用域的枚举类型仅在枚举类型内可见。它们只能通过cast强制类型转换以转换至其他类型。
  • 限定作用域的枚举类型和不限范围的枚举类型都支持底层类型指定。限域的枚举类型的默认底层类型是int,而不限域的枚举类型没有默认底层类型。
  • 限定作用域的枚举类型总是可以进行前置声明,而不限域的枚举类型却只有在指定了默认底层类型的前提下才可以进行前置声明。

原著在线阅读地址

相关推荐
AI的探索之旅27 分钟前
97 个 OpenCV 实例(三十七):二维码识别,一张图四个码的陷阱
c++·opencv·计算机视觉
UIU1141 小时前
P16237 [蓝桥杯 2026 省 B] 应急布线
数据结构·c++·算法·蓝桥杯
raindayinrain1 小时前
c++设计模式
开发语言·c++·设计模式
旖旎夜光1 小时前
LeetCode 315: 算右侧小于当前元素的个数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
D e re k ZH1 小时前
保序回归:从 PAVA 到可并堆的单调约束优化
c++·算法·数据挖掘·回归·kotlin
青少儿编程课堂1 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:字典树前缀树与单词检索统计
c++·python·算法·bfs·信息学竞赛
octopus_c2 小时前
C++ 运算符重载详解 && 日期类的实现
开发语言·c++
青少儿编程课堂2 小时前
2026蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:堆排序与优先队列解析
c++·python·算法·bfs·信息学竞赛
j7~2 小时前
【C++ 面试笔记】(篇二):类与数据抽象 & STL 容器与算法
开发语言·c++·面试·stl容器和算法·类和数据抽象
Fuyo_11193 小时前
深入解析 C++ unordered_set 与 unordered_map 的核心特性与性能实战
开发语言·c++