转向现代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; 
}
相关推荐
武子康16 小时前
Java-20 深入浅出 MyBatis - 手写ORM框架1 从原始 JDBC 暴露的 6 大问题开始
java·后端
特种加菲猫17 小时前
C++11核心特性深度解析:从列表初始化到lambda与包装器
开发语言·c++
qq_25183645717 小时前
2026计算机毕设选题|3000套高质量SpringBoot实战项目(含完整源码)(每人一套不收米)
java·spring boot·课程设计
设计师小聂!17 小时前
Java异常处理
java·开发语言·后端·编辑器·idea
SimonKing17 小时前
实用,DynamicTP进阶之数据采集与告警
java·后端·程序员
用户2986985301417 小时前
Java 进阶:基于模板生成 Word 文档的实践思路
java·后端
涛声依旧-底层原理研究所17 小时前
响应式编程:map与flatMap实战解析
java
枕星而眠17 小时前
C++ 面向对象核心机制深度解析:多态性、虚函数、虚继承与 final 类
运维·开发语言·c++·后端
智者知已应修善业17 小时前
【51单片机8个LED,已经使用了D1D2,怎么样在不动D1D2的前提下实现D6~D8的流水灯】2024-1-19
c++·经验分享·笔记·算法·51单片机
坚果派·白晓明17 小时前
鸿蒙PC适配实战:simdjson 三方库移植攻略与 AtomCode Skills 提效之道
c++·harmonyos·三方库·skills·atomcode·c/c++三方库·c/c++三方库适配