Item10 Prefer scoped enums to unscoped enums

   通常来说我们在花括号中定义的名称其作用域就在花括号中,但是C++98的枚举类型的声明却不遵从这个规则。

enum Color {black,white,red};
auto white = false;   //编译出错white已经声明了

   事实上,上面这些枚举名称都暴露到外层的作用域中了,官方称这种枚举类型成为unscoped,也就是无作用限制的,在C++11中引入了scoped的枚举类型,枚举的名称不会暴露到外层作用域中。

enum class Color {black,white,red};
auto white = false;
Color c = Color::white;

   通过使用scoped的枚举类型,可以减少因为作用域的问题带来的名称污染,除了作用域外,scoped的枚举类型还有另外一个优点就是强类型,不会进行隐式类型转换,而unscoped的枚举类型可以隐式转换为整型。

enum Color {black,white,red};
Color c = red;
if (c < 14.5) {         //和浮点型进行比较
    .....
}

enum class CColor {black,white,red};
CColor cc = Color::red;
if (cc < 14.5) {        //编译出错,无法进行隐式类型转换。
    ....
}

   如果非要进行转换的化,可以借助与static_cast来进行显示的转换,除了上面提到的几个优点外,scoped类型还可以前向声明,这样可以加快编译的速度,看到这里的读者可能会问unscoped的枚举类型难道不能前向声明吗?

enum Color;         //编译不通过
enum class Color;   //编译通过

   通过实验,果不其然unscoped的枚举类型不支持前向声明!,好吧我承认我在这里说了一个慌,它是支持前向声明的,只不过不是这样前向声明的而已,接下来让我们解析下。前向声明,其实就是提前声明,而声明就是告诉目标是什么类型,长什么样子。具体的内容等用到了再去看它的定义。然后有一个事实大多数人却不知道,unscoped枚举类型的实际类型并不是enum,它有一个底层存储类型。而这个底层存储类型是编译器在编译的时候决策的,根据你的取值范围来定义你的底层存储类型。

enum Color {black,white,red};

   上面的Color的实际存储类型可能就是char类型,足够存储了。这一切的目的就是为了节省空间。也就是unscoped的实际类型是不定的是编译器负责选择,所以你前向声明的时候也就没办法指明其类型了。所以不支持上面这种方式来前向声明,但是可以在指定底层存储类型的情况下进行前向声明。有的读者可能好奇为什么C++11中引入的scoped枚举类型可以前向声明,还有如何指定底层存储类型。对于第一个问题,答案很简单,那就是scoped的底层存储类型是默认的,默认是int,我们也可以指定其它的存储类型。

enum class Color: std::uint32_t {black,white,red}

那么unscoped枚举类型如何指定底层存储类型呢?

enum Color : std::uint8_t {black,white,red}

enum Color : std::uint8_t;      //前向声明

   上面对比了两种不同的枚举类型,scoped优势明显,但是unscoped也有自己的优点,它的优点我认为最为重要的一点就是把一些无意义的数值有意义化,典型的比如函数返回值,下标位置等。

using UserInfo = std::tuple<std::string,std::string,std::size_t>;
UserInfo uInfo;
auto val = std::get<1>(uInfo)

   上面使用了C++11中的tuple来表示一个用户的姓名,email,和年龄等信息,通过std::get<1> 取出email信息,很明显数值1在这里是无意义的不易读,如果换成枚举类型就会很易读了。

enum UserInfoFields { uiName,uiEmail,uiAge };
auto val = std::get<uiEmail>(uInfo)

但是如果上面的代码换成使用scoped枚举类型的化就会显得臃肿。

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

   需要先转换为std::size_t类型,这里有点投机取巧,不应该转换为std::size_t类型的,应该转换为枚举类型的底层存储类型,因为如果底层存储类型比较大,转换成std::size_t可能会导致窄化。为此需要有个手段获取枚举类型的底层存储类型。而且还需要是编译时获取,因为获取的值是作为std::get这个模板的参数。

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

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值