C++11枚举类——enum class

  • enum现在被称为不限范围的枚举型别
  • enum class是限定作用域枚举型别,他们仅在枚举型别内可见,且只能通过强制转换转换为其他型别。
  • 两种枚举都支持底层型别指定,enum class默认是int,enum没有默认底层型别
    enum可以前置声明,enum仅在指定默认底层型别的情况下才能前置声明

众所周知在C++中,变量名字仅仅在一个作用域内生效,出了大括号作用域,那么变量名就不再生效了。但是传统C++的enum却特殊,只要有作用域包含这个枚举类型,那么在这个作用域内这个枚举的变量名就生效了。即枚举量的名字泄露到了包含这个枚举类型的作用域内。在这个作用域内就不能有其他实体取相同的名字。所以在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作用域仅在大括号内生效

auto white = false;		//正确,这个white并不是Color中的white

Color c = white;	//错误,在作用域范围内没有white这个枚举量

Color c = Color::white;	//正确

auto c = Color::white;	//正确

枚举类优势——避免发生隐式转换

传统的不限范围的枚举类是可以发生隐式转换的:

enum Color{black,white,red};
std::vector<std::size_t> primeFactors(std::size_t x);	//函数返回x的质因数

Color c = red;

if(c < 14.5)	//将color型别和double型别比较,发生隐式转换
{
	auto factors = primeFactors(c);  //计算一个color型别的质因数,发生隐式转换
}

限定作用域的枚举型别不允许发生任何隐式转换。如果非要转换,按就只能使用static_cast进行强制转换。

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

if(c < 14.5)	//错误,不能将枚举类和double进行比较
{
	auto factors = primeFactors(c); //错误,Color不能转化为size_t型别
}

枚举类优势——可以前置声明

枚举类可以进行前置声明,即型别名字可以比其中的枚举量先声明:

enum Color;			//错误
enum class Color;	//正确

传统的不限作用域的枚举也可以前置声明,但是需要先做其他的工作。因为一切枚举型别在C++里都需要由编译器来选择一个整型型别作为其底层型别。为了节约使用内存,编译器通常会为枚举型别选用足够表示枚举量取值的最小底层型别。但某些情况下,编译器会用空间换取时间,他们可能不选择只具备最小可容尺寸的型别(当然同时要具备优化空间的能力)。在为了使这种设计成为可能,C++98就只提供了枚举型别的定义(即列出所有的枚举量),而不支持声明。这样编译器就可能在枚举类型使用前逐个的确定其底层型别选择哪一种。

这种前置声明的缺失会造成一些弊端。假设我们有一个枚举型别,如果我们在这个枚举中新增了一个变量,那么整个用到枚举型别的系统都会因此重新编译,哪怕整个系统只有一个函数用到了这个枚举型别中新的枚举量。而使用C++11的枚举类就可以避免:

enum class Status;

void f(Status s);

若头文件包含了上述这些声明,则即使Status定义发生了修改,甚至是新增了一个枚举变量,只要这个函数中并没有使用这个新增的枚举量,就不会重新编译。

之所以C++11中的枚举型别可以前置声明,是因为限定作用域的枚举型别底层是已知的。默认底层型别是int。如果觉得底层型别不满意,我们可以进行修改。不论是哪一种,编译器都能提前知道这个枚举量的尺寸。

enum class Status;		//默认底层型别是int

enum class Status:std::uint32_t; //修改底层型别为uint32_t

C++98中的枚举类型因为编译器不知道尺寸所以不允许前置声明,如果我们和C++11中一样也提前指定底层型别,那么eunm也可以前置声明了。(枚举型别的定义也可以指定底层型别)

enum Color:std:uint8_t;	//提前指定底层型别,可以进行前置声明

enum class Status:std::uint32_t{		//在定义中设置底层型别
	good = 0,
	failed = 1,
	incomplate = 100
};

C++98中enum的使用情况

传统的enum并非被完全取代了,在一种情况下它还是具有优势,即需要引用C++11中的std::tuple型别的各个域时。

//********文件A********//
using UserInfo = 
std::tuple<std::string, //名字
			std::string //邮件
			std::size_t>; //声望值

//********文件B*******//
UserInfo uInfo;

auto val = std::get<1>(uInfo);  //取用域1的值

在上述代码中,在文件B中我们要取tuple中第二个值,但是如果第一次接触这段代码,很难知道这第2个值到底是什么意思。使用不限范围的枚举型别和域序数关联就可以消除这种问题:

eunm UserInfoFields{uiName,uiEmail,uiReputation};

UserInfo uInfo;

auto val = std::get<uiEmail>(uInfo);  //一目了然,要去邮件这个值

以上代码能够运行的原理就是不限范围的枚举类型可以隐式转换。而使用enum class来定义的话,由于限定作用域的枚举类型不接受隐式转换,就要使用static_cast进行强转,整个代码会变得很啰嗦。当然,这种啰嗦的写法也确实能避免由于不限定作用域而带来的技术缺陷。


版权声明:本文为weixin_42817477原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。