C++11 中,枚举的关键字为 enum class,即在 enum 后加 class,与 C++98 的 "plain" enum 区别如下:
enum class Color { red, green, blue }; enum Color { red, green, blue };
1 enum class 的优点
1.1 防止命名空间污染
C++98 的 enum 是 "非域内枚举"(unscoped enums)
// yellow, green, blue are in same scope as Color enum Color { yellow, green, blue}; // error! yellow already declared in this scope auto yellow = false;
enum class 将 { } 内的变量,加上 class 限制其在 { } 作用域内可见,是"域内枚举" (scoped enums),可以防止命名空间污染
// yellow, green, blue are scoped to Color
enum class Color { yellow, green, blue};
// fine, no other "yellow" in scope
auto yellow = false;
// also fine
auto c = Color::yellow;
1.2 强类型枚举
非域内的枚举成员,可隐式的转换为广义整型 (integral types)
enum Color { yellow, green, blue}; // func. returning prime factors of x std::vector<std::size_t> primeFactors(std::size_t x); Color c = blue; if (c < 14.5) // compare Color to double (!) { auto factors = primeFactors(c); // compute prime factors of a Color (!) ... ... }
而域内的枚举成员,不能隐式的转换为广义整型
enum class Color { yellow, green, blue }; Color c = Color::blue; // error! can't compare Color and double if (c < 14.5) { auto factors = primeFactors(c); // error! can't pass Color to function expecting std::size_t ... ... }
正确的方式是用 C++ 的类型转换符,如 static_cast<>()
if (static_cast<double>(c) < 14.5) // odd code, but it's valid { auto factors = primeFactors(static_cast<std::size_t>(c)); // suspect, but it compiles ... ... }
1.3 前置声明
enum class 支持前置声明,即不用初始化枚举成员,声明一个枚举类型
enum class Color;
1) 新增枚举成员
enum 在声明时,编译器会选择占用内存最小的一种潜在类型 (underlying types),来代表每一个枚举成员
// compiler may choose char type enum Color { black, white, red };
下例中,编译器可能会选择更大的能包含 0 ~ 0xFFFFFFFF 范围的潜在类型
enum Status { good = 0, failed = 1, incomplete = 100, corrupt = 200, indeterminate = 0xFFFFFFFF };
非前置声明的缺点是,当新增一个枚举成员时 (如下 audited ),整个系统将会被重新编译一遍,即使只有一个很简单的函数使用了新加的枚举成员 (audited)
enum Status { good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF };
而使用前置声明,当新增枚举成员时,包含这些声明的头文件并不需要重新编译,源文件则根据新枚举成员的使用情况来决定是否重新编译
如下,Status 中新增枚举成员 audited,如果函数 continuteProcesing 没有使用 audited,则函数 continuteProcesing 的实现并不需要重新编译
// forward declaration enum class Status; // use of fwd-declared enum void continueProcessing(Status s);
2) 潜在类型
enum class 的潜在类型 (underlying type),缺省为 int 型,当然也可自定义潜在类型。无论哪种方式,编译器都会预先知道枚举成员的大小
// underlying type is int enum class Status; // underlying type for Status is std::uint32_t (from <cstdint>) enum class Status: std::uint32_t; // specify underlying type on enum's definition enum class Status: std::uint32_t { good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF };
2 enum 的优点
一般情况下,优先用 enum class,但在某些特定情况下(如 std::tuple),enum 反而有一定的优势
假定社交网站中,每一位用户,都使用一种模板类型 - 元组 (tuple) 来表示名字、邮箱、声望值 (name, email, reputation value)
// type alias using UserInfo = std::tuple<std::string, std::string, std::size_t> ;
当如下代码,在另一个源文件里时,很有可能已经忘记了 tuple 中第一个成员的含义,是名字还是邮箱?
// object of tuple type UserInfo uInfo; ... // get value of field 1 auto val = std::get<1>(uInfo);
2.1 非域内枚举
用 enum class,则不必担心忘记 tuple 中的顺序
enum UserInfoFields { uiName, uiEmail, uiReputation }; UserInfo uInfo; ... ... // get value of email field auto val = std::get<uiEmail>(uInfo);
2.2 域内枚举
用 enum class,则会涉及类型转换,看上去比较繁琐
enum class UserInfoFields { uiName, uiEmail, uiReputation }; UserInfo uInfo; // as before … auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
此时,可用模板函数,将枚举成员 UserInfoFields::uiEmail 和 std::size_t 类型联系起来
template<typename E> constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept { return static_cast<typename std::underlying_type<E>::type>(enumerator); }
这样,稍微缩减了代码的复杂度,但相比于 enum,还是有些繁琐
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
小结
1) C++98 的 enum 是“非域内的”;而 C++11 的 enum class 是“域内的”,限制了枚举成员只在域内可见
2) enum class 的缺省潜在类型 (underlying type) 是 int 型,而 enum 没有缺省潜在类型
3) enum class 一般总是前置声明,而 enum 只有在指定了潜在类型时才可以是前置声明
参考资料
《Effective Modern C++》Item 10
《C++ Programming Language》4th, ch 8.4 Enumerations
Prefer class enums over "plain" enums
原文链接: http://www.cnblogs.com/xinxue/
专注于机器视觉、OpenCV、C++ 编程