优先选定限定作用域的枚举类型,而非不限作用域的枚举类型
先说一个通用规则,如果在一对大括号里面声明了一个名字,则该名字的可见性就被限定在括号括起来的作用域内,但这个规则不适用于c++98风格的枚举类型中定义的枚举量。这些枚举量的名字属于包含着这个枚举类型的作用域,这就意味着在此作用域中不能有其他实体取相同的名字;
enum Color { black, white, red }; //black,white,red所在作用域和Color相同
auto white = false; //错误,white已经在范围内被声明过了
在C++11中,存在有限定作用域的枚举类型,不会泄露名字。
enum class Color { black, white, red }; //black,white,red被限定在Color作用域内
auto white = false; //没问题,范围内再无其他white
Color c = white; //错误,范围内并无white的枚举量
Color c = Color::white; //没问题
auto c = Color::white; //没问题
由于限定作用域的枚举类型时通过enum class
,所以有时它们也被称为枚举类。
限定作用域的枚举类型将名字空间的污染降低。除此之外,限定作用域的枚举类型的枚举量是更强类型的。不限范围的枚举类型中的枚举量可以隐式转换到整数类型(并能够从此处进一步转换到浮点类型)。
enum Color { black, white, red }; //black,white,red被限定在Color作用域内
std::vector<std::size_t> primeFactor(std::size_t x);
Color c = red;
if(c < 14.5)
{
auto factors = primeFactors(c);
}
从限定作用域的枚举类型到任何其他类型都不存在隐式转换路径。
enum class Color { black, white, red }; //限定作用域的枚举类型
Color c = Color::red;
if(c < 14.5) //错误,不能将Color类型和double类型比较
{
auto factors = primeFactors(c); //不能将Color类型传入
}
如果真的想要实施一个从Color到另一个类型的转换,实施一个强制类型转换即可。
if(static_cast<double>(c) < 14.5)
{
auto factors = primeFactors(static_cast<std::size_t>(c));
}
限定作用域的枚举类型可以进行前置声明,即其类型名字可以比其中的枚举量先声明。
enum Color; //错误
enum class Color; //没问题
在c++11中,不限范围的枚举类型也可以进行前置声明,但必须在完成一些额外工作之后。这些额外工作由以下事实带来,一切枚举类型在C++里都会由编译器来选择一个整数类型作为其底层类型
enum Color { black, white, red };
编译器会选择char
作为其底层类型。
对于
enum Status{
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
indeterminate = 0xFFFFFFFF
};
这里,需要表示的取值范围是从0到0xFFFFFFFF。
为了节约内存,编译器通常都会为枚举类型选用足够表示枚举量取值的最小底层类型。在某些情况下,编译器会用空间换取时间,而在这样的情况下,它们可能会不选择只具备最小可容尺寸的类型,但是它们当然需要具备优化空间的能力。为了使这种设计称为可能,c++98就只提供了枚举类型定义的支持,枚举类型声明则不允许。这么一来,编译器就可能在枚举类型被使用前,逐个地确定其底层类型选择哪一种。
前置声明能力的缺失会增加编译依赖性。
enum Status{
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500,
indeterminate = 0xFFFFFFFF
};
可能整个系统都会因此而重新编译。即使只有一个子系统(可能是一个函数)用到了这个新的枚举量。而这种事利用c++11中为枚举类型提供的前置声明即可破除。
举例来说
enum class Status; //前置声明
void continueProcessing(Status s); //取用前置声明的枚举类型
若头文件包含了这些声明,则在Status定义发生了修改时,就不会要求重新编译。即使Status被修改了(加了一个audited枚举量),但是continueProcessing的行为未受影响,则continueProcessing的实现也同样无须重新编译。
限定作用域的枚举类型的底层类型是已知的;对于不限范围的枚举类型,你可以指定这个底层类型。
默认的,限定作用域的枚举类型的底层类型是int。
enum class Status; //底层类型是int
如果默认类型不合你意,你可以推翻它。
enum class Status : std::uint32_t; //status的底层类型是std::uint32_t
采用这两种方法中的任何一种,编译器都能知晓限定作用域的枚举类型中的枚举量尺寸。
如果要指定不限范围的枚举类型的底层类型,做法和限定作用于的枚举类型一致,这样做了之后,不限范围的枚举类型也能够进行前置声明了。
enum Color : std::uint8_t; //不限范围的枚举类型的前置声明
底层类型指定同样也可以在枚举类型定义中进行
enum class Status : std::uint32_t{
good = 0,
failed = 1,
incomplete = 100,
corrupt = 200,
audited = 500,
indeterminate = 0xFFFFFFFF
};
当需要引用c++11中的std::tuple
类型的各个域时,不限范围的枚举类型还是有用的。
假设要为一个社交网站准备一个元组来持有名字,电子邮件和声望值
using UserInfo =
std::tuple<std::string, //名字
std::string, //email地址
std::size_t>; //声望值
虽然注释说明了tuple
各个字段对应的意思,但是当你在另一个文件遇到下面的代码时之前的注释就不是那么有用的了。
UserInfo uInfo;
auto val = std::get<1>(uInfo); //获取第一个字段
作为程序员,真的应该记住域1对应的用户的电子邮件么?
采用一个不限范围的枚举类型和域序数关联就可以消除这种记忆需要。
enum UserInfoFields { uiName,uiEmail,uiReputation };
UserInfo uInfo;
auto val = std::get<uiEmail>(uInfo);
以上代码能够运行,是因为UserInfoFields
向std::size_t
的隐式类型转换,转换结果就是std::get
要求的类型。
而采用限定作用域的枚举类型版本对应的代码就啰嗦多了。
enum class UserInfoFields { uiName,uiEmail,uiReputation };
UserInfo uInfo;
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
要想不那么啰嗦,就得写一个函数,以枚举量为形参并返回其对应的std::size_t
类型的值。std::get
是个模板,而你传入的值是个模板形参,所以这个将枚举量变换成std::size_t
类型值的函数必须在编译期就是计算出结果。条款15说明,这就意味着必须使用constexpr
函数。
其实,光constexpr
函数还不够,因为它需要配合任何枚举类型,因此还得用constexpr
函数模板。如果做了这样的泛化,那返回值也需要泛化才行,这样就不能返回std::size_t
,而需要返回枚举类型的底层类型才可以。这个类型可以通过std::underlying_type
类型特征取得。
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumerator) noexcept
{
return
static_cast<typename std::underlying_type<E>::type>(enumerator);
}
toUType
让我们可以用下面的方式来访问元组中的一个域。
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
要点速记
- c++98风格的枚举类型,现在称为不限范围的枚举类型
- 限定作用域的枚举类型仅在枚举类型内部可见。它们只能通过强制类型转换来转换到其他类型
- 限制作用域的枚举类型和不限范围的枚举类型都支持底层类型指定。限定作用域的枚举类型的默认底层类型是int,而不限范围的枚举类型没有默认底层类型。
- 限定作用域的枚举类型总是可以进行前置声明,而不限范围的枚举类型却只有在指定了默认底层类型的前提下才可以进行前置声明