|
想要查看内容赶紧注册登陆吧!
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
通常来说我们在花括号中定义的名称其作用域就在花括号中,但是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);
复制代码
|
|