转向现代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 小时前
C++学习笔记17:析构函数
c++·学习笔记·类和对象·构造函数·析构函数·动态内存
不是光头 强3 小时前
Java 后端实战进阶:从踩坑到架构的系统化笔记
java·笔记·架构
ID_180079054734 小时前
企业级淘宝评论 API最简说明,JSON 返回示例
java·服务器·前端
Plan-C-4 小时前
二叉树的遍历
java·数据结构·算法
历程里程碑4 小时前
54 深入解析poll多路复用技术
java·linux·服务器·开发语言·前端·数据结构·c++
无限进步_4 小时前
【C++】可变参数模板与emplace系列
java·c++·算法
计算机安禾5 小时前
【c++面向对象编程】第28篇:new/delete vs malloc/free:C++中正确动态内存管理
开发语言·c++·算法
qeen875 小时前
【算法笔记】各种常见排序算法详细解析(下)
c语言·数据结构·c++·笔记·学习·算法·排序算法
逐光老顽童5 小时前
Java 内存模型深度解析与 JVM 调优实战指南
java·架构