转向现代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; 
}
相关推荐
HelloWorld工程师几秒前
@Primary 注解
java·spring
aqiu1111112 小时前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
h39747 小时前
DirectShow过滤器开发-视频源过滤器
c++·windows·音视频·directshow
程序猿编码8 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
西飘客9 小时前
vs 怎么根据dll 生成lib文件
c++·qt
代码什么用10 小时前
Spring基础使用
java·后端·spring
程序员老陆10 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
Su米苏10 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园10 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁10 小时前
Java 异常体系详解:从字节码原理到最佳实践
java