转向现代C++——优先选用限定作用域的枚举型别,而非不限作用域的枚举型别

文章目录

优先选用限定作用域的枚举型别,而非不限作用域的枚举型别

在 C++98 中,我们使用的 enum 是不限作用域的(unscoped enum) ;而 C++11 引入了通过 enum class` 声明的限定作用域的枚举(scoped enum)

绝大多数情况下选择 enum class。它能帮你屏蔽掉大量由于隐式类型转换和命名冲突导致的低级 Bug。

名字空间污染

不限作用域的枚举(enum)中定义的名字,会直接泄漏到该枚举所在的作用域中。这意味着你不能在同一个作用域内定义同名的枚举常量。

cpp 复制代码
enum Color { black, white, red };

// 编译错误!"black" 已经在这个作用域被定义过了
// enum class Astronomy { black, white, red }; 

auto white = false; // 编译错误!"white" 已被用作枚举常量

enum class 的名字只在枚举内部可见,必须通过作用域解析运算符(::`)来访问,解决了命名冲突。

cpp 复制代码
enum class Color { black, white, red };
enum class Light { google, white, baidu }; // white 不会冲突

Color c = Color::black; // 必须加上 Color::
Light l = Light::white; // 必须加上 Light::

auto white = false;     //此时的 white 是一个独立的变量

强类型安全与隐式转换

不限作用域的枚举会极其隐式地转换为整型(甚至浮点型),这经常导致不合逻辑的代码能够通过编译。而 enum class` 是强类型的,绝不隐式转换

cpp 复制代码
enum Color { black, white, red };

Color c = red;

// 荒谬的比较:颜色竟然可以和数字、浮点数直接比大小!
if (c < 14.5) { 
    // 编译通过!red 被隐式转换成了整数 2
    auto x = c * 10; // 编译通过!允许算术运算
}
cpp 复制代码
enum class Color { black, white, red };

Color c = Color::red;

// if (c < 14.5) { } // 编译错误!不能将 Color 与 double 进行比较

// if (c == 2) { }   // 编译错误!不能将 Color 与 int 进行比较

// 如果真的需要转换,必须进行显式类型转换(static_cast)
if (static_cast<int>(c) == 2) {
    // 显式转换,安全且意图明确
}

前置声明

在 C++ 中,能够前置声明一个类型可以减少头文件包含,从而加快编译速度

:::info

  • 传统 enum :通常不能前置声明。因为编译器需要知道枚举的最大值,以此来决定底层用多大的整数类型(char、int 还是 short`)来存储它。

:::

:::success

  • enum class天生支持前置声明。因为它的默认底层类型(underlying type)是固定的(标准规定默认为 int`)。

:::

plain 复制代码
// ==================== 头文件 Widget.h ====================
enum class Status; // 完美支持前置声明!不需要知道具体有哪些枚举值

class Widget {
    Status s;      // 编译器知道 Status 默认是 int 大小(4字节),可以确定 Widget 大小
};

// ==================== 实现文件 Widget.cpp ====================
enum class Status { Good, Failed, Unknown }; // 在这里才给出具体定义

:::color4

📌**补充说明:C++11 也允许传统的 enum 进行前置声明,但你必须显式指定底层类型**,如 enum Color: uint8_t;。而enum class 不需要显式指定,默认就是 int`。

:::


特例:什么时候不限作用域的 enum` 更好?

📢**当我们需要使用 std::tuple 并通过语义化的名字去获取里面的元素时,传统 ****enum**`** 的隐式转换反而成了一种"便利"。**

cpp 复制代码
#include <tuple>
#include <string>

using UserInfo = std::tuple<std::string,  // 姓名
                    std::string,  // 邮箱
                    std::size_t>; // 声望值

// 使用传统的 unscoped enum
enum UserFields { uName, uEmail, uRep };

void processUser() {
    UserInfo u("Alice", "alice@email.com", 99);

    // 隐式转换为了 std::size_t,语法非常自然
    auto name = std::get<uName>(u); 
}
cpp 复制代码
enum class UserFields { uName, uEmail, uRep };

void processUser() {
    UserInfo u("Alice", "alice@email.com", 99);

    // 极其恶心、冗长的语法:必须 static_cast
    auto name = std::get<static_cast<std::size_t>(UserFields::uName)>(u);
}
现代 C++ 的替代方案(C++17 结构化绑定)

在现代 C++(C++17 及以后)中,即使是上述特例,我们也不再依赖传统 enum` 了,因为我们有了结构化绑定,它比上面两种方法都更优雅:

cpp 复制代码
void processUser() {
    UserInfo u("Alice", "alice@email.com", 99);

    // 直接解包,连枚举都不需要定义了
    auto [name, email, rep] = u; 
}
相关推荐
鹿角片ljp6 小时前
LeetCode 64:最小路径和复盘|二维 DP 与 ACM 模式完整写法
java·数据结构·算法
泡海椒7 小时前
PDF 表格样式优化:jquick-pdf 边框、圆角、背景色
java·开发语言·pdf
景熙55238 小时前
15.Java 8 Stream 流入门到实战
java·开发语言·数据结构
wno7048 小时前
Spring Security权限控制
java·python·spring
汉克老师9 小时前
GESP2026年9月认证C++六级( 第三部分编程题(1、数组划分))精讲
c++·gesp·小学生·学c++编程
千里码aicood9 小时前
基于PLC与机器视觉的智能分拣控制系统设计
c++·自动化·plc
杨运交9 小时前
[071][验证码模块]基于Spring拦截器的验证码认证设计思想
java·后端·spring
Geek-Chow9 小时前
CountDownLatch in Java
java
漂流瓶jz9 小时前
UVA-1442 洞穴 题解答案代码 算法竞赛入门经典第二版
c++·算法·题解·aoapc·算法竞赛入门经典·uva