Item10 Prefer scoped enums to unscoped enums

[复制链接]

该用户从未签到

759

主题

763

帖子

4660

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4660
跳转到指定楼层
楼主
发表于 2018-6-4 16:11:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

想要查看内容赶紧注册登陆吧!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
通常来说我们在花括号中定义的名称其作用域就在花括号中,但是C++98的枚举类型的声明却不遵从这个规则。
  1. enum Color {black,white,red};
  2. auto white = false;   //编译出错white已经声明了
复制代码
事实上,上面这些枚举名称都暴露到外层的作用域中了,官方称这种枚举类型成为unscoped,也就是无作用限制的,在C++11中引入了scoped的枚举类型,枚举的名称不会暴露到外层作用域中。
  1. enum class Color {black,white,red};
  2. auto white = false;
  3. Color c = Color::white;
复制代码
通过使用scoped的枚举类型,可以减少因为作用域的问题带来的名称污染,除了作用域外,scoped的枚举类型还有另外一个优点就是强类型,不会进行隐式类型转换,而unscoped的枚举类型可以隐式转换为整型。
  1. enum Color {black,white,red};
  2. Color c = red;
  3. if (c < 14.5) {         //和浮点型进行比较
  4.     .....
  5. }

  6. enum class CColor {black,white,red};
  7. CColor cc = Color::red;
  8. if (cc < 14.5) {        //编译出错,无法进行隐式类型转换。
  9.     ....
  10. }
复制代码
如果非要进行转换的化,可以借助与static_cast来进行显示的转换,除了上面提到的几个优点外,scoped类型还可以前向声明,这样可以加快编译的速度,看到这里的读者可能会问unscoped的枚举类型难道不能前向声明吗?
  1. enum Color;         //编译不通过
  2. enum class Color;   //编译通过
复制代码
通过实验,果不其然unscoped的枚举类型不支持前向声明!,好吧我承认我在这里说了一个慌,它是支持前向声明的,只不过不是这样前向声明的而已,接下来让我们解析下。前向声明,其实就是提前声明,而声明就是告诉目标是什么类型,长什么样子。具体的内容等用到了再去看它的定义。然后有一个事实大多数人却不知道,unscoped枚举类型的实际类型并不是enum,它有一个底层存储类型。而这个底层存储类型是编译器在编译的时候决策的,根据你的取值范围来定义你的底层存储类型。
  1. enum Color {black,white,red};
复制代码
上面的Color的实际存储类型可能就是char类型,足够存储了。这一切的目的就是为了节省空间。也就是unscoped的实际类型是不定的是编译器负责选择,所以你前向声明的时候也就没办法指明其类型了。所以不支持上面这种方式来前向声明,但是可以在指定底层存储类型的情况下进行前向声明。有的读者可能好奇为什么C++11中引入的scoped枚举类型可以前向声明,还有如何指定底层存储类型。对于第一个问题,答案很简单,那就是scoped的底层存储类型是默认的,默认是int,我们也可以指定其它的存储类型。
  1. enum class Color: std::uint32_t {black,white,red}
复制代码
那么unscoped枚举类型如何指定底层存储类型呢?
  1. enum Color : std::uint8_t {black,white,red}

  2. enum Color : std::uint8_t;      //前向声明
复制代码
上面对比了两种不同的枚举类型,scoped优势明显,但是unscoped也有自己的优点,它的优点我认为最为重要的一点就是把一些无意义的数值有意义化,典型的比如函数返回值,下标位置等。
  1. using UserInfo = std::tuple<std::string,std::string,std::size_t>;
  2. UserInfo uInfo;
  3. auto val = std::get<1>(uInfo)
复制代码
上面使用了C++11中的tuple来表示一个用户的姓名,email,和年龄等信息,通过std::get<1> 取出email信息,很明显数值1在这里是无意义的不易读,如果换成枚举类型就会很易读了。
  1. enum UserInfoFields { uiName,uiEmail,uiAge };
  2. auto val = std::get<uiEmail>(uInfo)
复制代码
但是如果上面的代码换成使用scoped枚举类型的化就会显得臃肿。
  1. auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
复制代码
需要先转换为std::size_t类型,这里有点投机取巧,不应该转换为std::size_t类型的,应该转换为枚举类型的底层存储类型,因为如果底层存储类型比较大,转换成std::size_t可能会导致窄化。为此需要有个手段获取枚举类型的底层存储类型。而且还需要是编译时获取,因为获取的值是作为std::get这个模板的参数。
  1. template<typename E>
  2. constexpr auto toUType(E enumerator) noexcept
  3. {
  4.     return static_cast<std::underlying_type_t<E>>(enumerator);
  5. }

  6. auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
复制代码



分享到:  QQ好友和群QQ好友和群
收藏收藏
回复

使用道具 举报

快速回复高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表