文章

条款10:优先考虑限域enum而非未限域enum

enum class 更安全,避免作用域污染和隐式转换。

条款10:优先考虑限域enum而非未限域enum

条款10:优先考虑限域 enum 而非未限域 enum

作用域区别

未限域枚举(unscoped enum,C++98风格)

枚举值(enumerator)直接泄漏到枚举所在的作用域中,可能导致命名冲突。

1
2
enum Color { black, white, red };  // black, white, red 在 Color 所在作用域
auto white = false;                // 错误,white重复定义

限域枚举(scoped enum,C++11引入)

枚举值仅在枚举类型内有效,必须通过 枚举名::枚举值 访问,不污染外部命名空间。

1
2
3
4
enum class Color { black, white, red };
auto white = false;                // 合法,命名无冲突
Color c = white;                  // 错误,必须 Color::white
Color c = Color::white;           // 正确

类型安全

未限域 enum

枚举值隐式转换为整数类型,可与整型或浮点数比较,容易引发类型错误或语义混乱。

1
2
enum Color { black, white, red };
if (c < 14.5) { ... }  // 合法但语义怪异

限域 enum

枚举值不会隐式转换为其他类型,防止错误使用,增强类型安全。

1
2
enum class Color { black, white, red };
if (c < 14.5) { ... }  // 编译错误

需要转换时必须显式转换:

1
static_cast<double>(c);

前置声明支持

未限域 enum

不能直接前置声明,或者必须明确指定底层类型。

1
2
enum Color;                 // 错误
enum Color : uint8_t;       // 合法,需指定底层类型

限域 enum

可以直接前置声明,且默认底层类型为 int,也可以指定底层类型。

1
2
enum class Status;                         // 合法,默认int底层类型
enum class Status : std::uint32_t;         // 指定底层类型

前置声明减少编译依赖,提高编译效率。

底层类型

  • 两者都支持指定底层类型,利于内存和性能优化。

  • 默认:

    • 未限域enum不一定是 int,取决于编译器和枚举值范围。

    • 限域enum默认是 int

兼容性与实用场景

  • 未限域enum 在某些情况下有优势,比如与模板参数要求整数类型(如 std::tuple 索引)结合使用时,枚举值可隐式转为整数。
  • 对于一般用途,建议使用限域enum,以避免命名污染和类型安全问题。

示例:使用未限域 enum 索引 std::tuple

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个非限域枚举,枚举成员名直接在作用域内可用
enum UserInfoFields { uiName, uiEmail, uiReputation };

// 使用 std::tuple 来组合用户信息,分别是名字、邮箱和声望值
using UserInfo = std::tuple<std::string, std::string, std::size_t>;

// 创建一个 UserInfo 类型的对象
UserInfo uInfo;

// 使用非限域枚举值作为索引访问 tuple 的对应元素
// 这里 uiEmail 隐式转换成整数 1,表示取 tuple 中的第二个元素(索引从0开始)
auto val = std::get<uiEmail>(uInfo);

对应的限域 enum 用法较繁琐:

1
2
3
4
5
6
// 定义一个限域枚举(enum class),枚举成员名被限定在 UserInfoFields 作用域内
enum class UserInfoFields { uiName, uiEmail, uiReputation };

// 使用 std::get 访问 tuple 元素时,必须显式将枚举值转换成整数索引
// 这里使用 static_cast 将枚举成员转换为 std::size_t 类型
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);

可用辅助函数简化转换:

1
2
3
4
5
6
7
8
9
10
11
// 模板函数:将任意枚举类型的枚举值转换为其底层整型值
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept {
    // std::underlying_type<E>::type 获取枚举 E 的底层整型类型
    // static_cast 将枚举值转换为底层整型值
    return static_cast<typename std::underlying_type<E>::type>(enumerator);
}

// 使用示例:利用toUType函数将限域枚举UserInfoFields的枚举值转为索引,访问tuple元素
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

结论和建议

  • C++98的 enum 即未限域 enum,易引发命名冲突和类型隐式转换问题。
  • C++11的限域 enum(enum class)限定枚举名作用域,避免命名污染,禁止隐式类型转换,更安全。
  • 限域 enum 支持前置声明,减少编译依赖。
  • 在少数需要枚举值隐式转换为整数的场景,可使用未限域 enum,但通常建议用限域 enum 配合显式转换。
  • 建议默认使用限域 enum
本文由作者按照 CC BY 4.0 进行授权