《Effective Modern C++》Item 10: Prefer scoped enums to unscoped enums.

引子

枚举是C中一个常见的基本类型,一般来说,我们声明一个变量,它的作用域仅存在于它外层花括号包围的范围内,但是这个限制对于枚举(准确说C++98版本的枚举)并不成立:

enum Color { black, white, red };   // black, white, red are in same scope as Color
auto white = false;                 // error! white already declared in this scope

在C++11中,我们有了新的枚举声明方式来解决这个问题。

正文

在C++98中这种枚举,我们称之为unscoped枚举。而C++11中我们用scopde枚举来解决上面的问题:

enum class Color { black, white, red };     // black, white, red are scoped to Color
auto white = false;                         // fine, no other "white" in scope

Color c = white;                            // error! no enumerator named "white" is in this scope
Color c = Color::white;                     // fine
auto c = Color::white;                      // fine

除了不会名称污染以外,scoped枚举的第二个好处就是“强类型“,不会进行隐式转换:

enum Color {black,white,red};

Color c = red;
if (c < 14.5) {         // error! can't compare Color and double
    ...
}

unscoped枚举则可以隐式转换成整型:

enum Color { black, white, red };

Color c = red;
if (c < 14.5) {         // fine
    ...
}

当然如果非要进行类似的转换,scoped枚举还是可以用static_cast转换的。

scoped枚举的第三个优点就是可以提前声明(forward-declared),即它们可以在不置顶枚举元素的情况下声明:

enum Color;               // error!
enum class Color;         // fine

当然准确说造成这种情况的原因是,这种情况下编译器无法确定unscoped枚举的类型,而此时scoped枚举是有默认类型的(int类型)。如果我们显示指定它们的类型,则都可以做到提前声明:

enum Color : std::uint8_t; 
enum class Color: std::uint32_t ;

提前声明的好处是,如果我们把声明放在头文件,而定义放在cpp文件,那么我们在添加了新的枚举元素的情况下,不需要重新编译大量代码。

当然,unscoped枚举也有一个好处:把无意义的数值有意义化,如函数返回值,数组下标等,使得代码具有很高的可读性。例如我们用一个std::tuple保存用户信息:姓名,电子邮件,名声值:

using UserInfo =                 
     std::tuple<std::string,        // name
     std::string,                   // email
     std::size_t> ;                 // reputation

UserInfo uInfo; 
auto val = std::get<1>(uInfo);      

显然上面的代码是正确的,但是可读性并不高,事实上我们很难从最后std::get<1>中看出得到的结果是用户姓名。但是利用unscoped枚举和它的隐式转换功能,我们写出可读性极高的代码:

enum UserInfoFields { uiName, uiEmail, uiReputation };

UserInfo uInfo;
auto val = std::get<uiEmail>(uInfo);

scoped枚举如果想这么做必须用static_cast进行强制转换,并不这么优雅。当然我们可以用一个模板函数,std::underlying_typeconstexpr来帮我们处理这个问题:

template<typename E>                    // C++11 style
   constexpr typename std::underlying_type<E>::type
     toUType(E enumerator) noexcept
{
  return static_cast<typename
                   std::underlying_type<E>::type>(enumerator);
}

template<typename E>                    // C++14 constexpr auto
     toUType(E enumerator) noexcept
{
  return static_cast<std::underlying_type_t<E>>(enumerator);
}

最后使用时如下:

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

这样好了很多,但是仍然很臃肿,但是这种情况兼顾了防止名称污染的优点。

总结

  1. C++98中的枚举是无作用域限制的。
  2. (C++11中的)scoped枚举类是有作用域限制的,只能用cast进行显式类型转换。
  3. 两种枚举类型都支持指定底层的存储类型,对于scoped枚举来说默认是int,而unscoped枚举是未知的,需要编译器进行选择。
  4. scoped枚举总是可以提前声明的,而`unscoped枚举必须是在明确指定其底层存储的时候才能进行提前声明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值