转向现代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; 
}
相关推荐
咖啡八杯3 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式
优橙教育3 小时前
5G网优培训 vs Java开发:转行选哪个?
java·开发语言·5g
糖果店的幽灵3 小时前
【DeepAgents 从入门到精通】Context Management 上下文管理
java·人工智能·后端·spring·中间件·langgraph·deepagents
同勉共进4 小时前
记一例 vibe coding + gcc bug 导致的线程池死锁问题
c++·线程池·gcc·死锁·vibe coding
我叫洋洋4 小时前
C ++ [ hello world ]
c语言·c++·算法
腻害兔4 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:字典、短信、邮件、通知——后台系统的“基础设施四件套“!
java·前端·vue.js·产品经理·ai编程
碎光拾影5 小时前
ARM交叉工具链各工具作用及IMX6ULL平台LED+蜂鸣器裸机程序实现
java·开发语言·数据库
Miao121316 小时前
微服务 API 测试实践:海外某民宿平台如何构建模式驱动测试基础设施
java·开发语言
腻害兔6 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:支付模块 yudao-module-pay,一个让产品经理都看懂的支付中台设计
java·前端·vue.js·产品经理·ai编程
CRMEB系统商城7 小时前
开源自建还是SaaS订阅?算一笔3年经济账
java·大数据·开发语言·开源