[转]C++11的enumclassenumstruct和enum
1. 旧版enum存在的问题
问题描述
1向整形的隐式转换(Implicit conversion to an integer)
2⽆法指定底层所使⽤的数据类型(Inability to specify underlying type)
3enum的作⽤域(Scope)
4不同编译器解决该问题的⽅法不统⼀
1.1 问题1:向整形的隐式转换
在开始这个问题之前,我们需要知道什么是整形提升
查看之前的博⽂:
在看完什么是整形提升之后,我们开始这个问题:
旧版enum其实并不具有⾮常完全的类型安全(当然它也体现了⼀定的类型安全:1.禁⽌不同枚举体之间的赋值 2.禁⽌整形向枚举体的隐式转换等),也就是⾯对整形提升,旧版enum是没有抗拒⼒的。
例如:
#include <iostream>
enum colorA{redA,greenA,grayA};
enum colorB {redB,greenB,yellowB};
void test(int data) {
std::cout << "test called" << std::endl;
}
int main() {
colorA ca(redA);
colorB cb(greenB);
//ca = cb; ERROR , ⽆法从“colorB”转换为“colorA”
//ca = 2;  ERROR , ⽆法从“int”转换为“colorA”
bool res(ca < cb); //OK
std::cout << std::boolalpha << res << std::endl;
test(ca); //OK
std::();
return 0;
}
运⾏结果:
true
test called
就像上⾯的代码:我们仍然可以⽐较两个不同枚举体的⼤⼩,⽤枚举体调⽤参数为int的函数。显然此时的枚举体发⽣了整形提升。在⽆法使⽤11新版enum的情况下,机制的程序员想到了:将enum封装到类的内部的⽅法。
#include <iostream>
class Color {
private:
enum _color { _red, _blue, _yellow, _black };
public:
explicit Color(const _color & other) {
value = value;
}
explicit Color(const Color & other) {
value = other.value;
}
const Color& operator=(const Color& other) {
value = other.value;
return *this;
}
static const Color red, blue, yellow, black;
_color value;
//operators
bool operator <(const Color & other) { return value < other.value; }
bool operator >(const Color & other) { return value > other.value; }
bool operator <=(const Color & other) { return value <= other.value; }
bool operator >=(const Color & other) { return value >= other.value; }
bool operator ==(const Color & other) { return value == other.value; }
//...
//conversion
int toint() { return value; }
};
//init static const Color obj
const Color Color::red(Color::_color::_red);
const Color Color::blue(Color::_color::_blue);
const Color Color::yellow(Color::_color::_yellow);
const Color Color::black(Color::_color::_black);
void test(int data) {
std::cout << "called" << std::endl;
}
int main() {
Color ca(Color::blue);
std::cout << ca.toint() << std::endl;
//ca = 2; ERROR, 没有到接受“int”类型的右操作数的运算符(或没有可接受的转换)
//test(ca); ERROR, ⽆法将参数 1 从“Color”转换为“int”
//bool res(ca > 2); ERROR,没有到接受“int”类型的右操作数的运算符(或没有可接受的转换)
std::();
return 0;
}
的确,封装在类中的enum能够抵抗整形提升。但是这种enum不同于POD(plain old data),⽆法放⼊寄存器当中,这会带来额外的开销。
1.2 问题2:⽆法指定底层所使⽤的数据类型
A. ⾸先,⽆法指定数据类型,导致我们⽆法明确枚举类型所占的内存⼤⼩。这种⿇烦在结构体当中尤为突出,特别是当我们需要内存对齐和填充处理的时候。
#include <iostream>
enum Version { Ver1 = 1, Ver2, Ver3 };
struct MyStruct {
MyStruct(Version ver) { this->Ver = ver; }
Version Ver;
//
};
int main() {
enum c++MyStruct m(Version::Ver1);
std::();
return 0;
}
  此时我们的解决办法还是:不使⽤enum
#include <iostream>
enum Version { Ver1 = 1, Ver2, Ver3 };
struct MyStruct {
MyStruct(Version ver) { this->Ver = ver; }
unsigned char Ver;//将enum Version转为unsigned char类型
//
};
int main() {
MyStruct m(Version::Ver1);
std::();
return 0;
}
B. 其次,当我们使⽤enum时,我们⽆法决定编译器底层是如何对待enum的(⽐如:signed和unsigned)。
#include <iostream>
enum MyEnum { num1 = 1, num2 = 2, numn = 0xFFFFFF00U };
int main() {
std::cout << num1 << std::endl;
std::cout << num2 << std::endl;
std::cout << numn << std::endl;
std::();
return 0;
}
VS2015运⾏结果:
1
2
-
256
CodeBlocks运⾏结果:
1
2
4294967040
在 numn=0xFFFFFF00U;中,我们希望0xFFFFFF00表现为unsigned。但是不同的编译器其标准也不同。这就给我们的程序带来了许多的不确定性。
在⽂档n2347中的例⼦:不同编译器对0xFFFFFFF0U的表现。
#include <iostream>
using namespace std;
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };
int main() {
cout << sizeof(E) << endl;
cout << "Ebig = " << Ebig << endl;
cout << "E1 ? -1 =\t" << (E1 < -1 ? "less" : E1 > -1 ? "greater" : "equal") << endl;
cout << "Ebig ? -1 =\t" << (Ebig < -1 ? "less" : Ebig > -1 ? "greater" : "equal") << endl;
}
1.3 问题3:enum的作⽤域
enum的中的 ” { } ” ⼤括号并没有将枚举成员的可见域限制在⼤括号内,导致enum成员曝露到了上⼀级作⽤域(块语句)中。
例如:
#include <iostream>
enum color{red,blue};//定义拥有两个成员的enum,red和blue在enum的⼤括号外部可以直接访问,⽽不需要使⽤域运算符。
int main() {
std::cout << blue << std::endl;
std::();
return 0;
}
运⾏结果:
1
-
就如上⾯的代码,我们可以在blue的⼤括号之外访问它,color的成员被泄露到了该⽂件的全局作⽤域中(虽然它尚不具备外部链接性)。可以直接访问,⽽不需要域运算符的帮助。
但是这不是关键,有时我们反⽽觉得⾮常⽅便。下⾯才是问题所在:
问题:⽆法定义同名的枚举成员
enum color { red, blue };
//enum MyEnum { red, yellow }; ERROR, 重定义;以前的定义是“枚举数”
如上⾯的代码所⽰:我们⽆法重复使⽤red这个标识符。因为它在color中已经被⽤过了。但是,它们明明就是不同的枚举类型,如果可以使⽤相同的成员名称,然后通过域运算符来访问的话,该有多好!就像下⾯这样:
color::red
但是这是旧版的enum⽆法做到的。
解决上述问题:利⽤命名空间
#include <iostream>
namespace spaceA {
enum color { red, blue };
}
namespace spaceB {
enum colorX { red, blue };
}
int main() {
std::cout << spaceA::red << std::endl;
std::cout << spaceB::blue << std::endl;
std::cout << std::boolalpha << (spaceA::red > spaceB::blue) << std::endl;
std::();
return 0;
}
运⾏结果:
1
false
-
是的,只要利⽤命名空间我们就能解决枚举体的成员重定义问题,但是添加了多余的⼀层命名空间,未免显得⿇烦
1.4 不同编译器解决该问题的⽅法不统⼀
在1.2中展⽰的图⽚告诉我们:有些编译器可能提供了相应的扩展来解决这些问题,但是有的编译器却没有,这使得我们的编程⾮常的不统⼀,有时候因为enum⽽削弱了程序的可移植性。
2. enum class 和 enum struct
2.1 enum class 和 enum struct 是等价的
2.2 声明
如⼤标题,枚举体的声明和定义使⽤ enum class或是enum struct,⼆者是等价的。使⽤enum class\enum struct不会与现存的enum关键词冲突。⽽且enum class\enum struct具有更好的类型安全和类似封装的特性(scoped nature)。
enum class color{red,green,yellow};
enum class colorx{red,green=100,yellow};
//....
2.3 类型转换
与整形之间不会发⽣隐式类型转换,但是可以强转。
#include <iostream>
enum class color { red, green, yellow};
int main() {
//int res(color::red); //ERROR , “初始化”: ⽆法从“color”转换为“int”
//color col(2);//ERROR , “初始化”: ⽆法从“int”转换为“color”
//强转
int res(static_cast<int>(color::red));//OK
color col(static_cast<color>(1));//OK
std::();
return 0;
}
2.4 指定底层数据类型(underlying type)
默认的底层数据类型是int,⽤户可以通过:type(冒号+类型)来指定任何整形(除了wchar_t)作为底层数据类型。
enum class color:unsigned char{red,blue};
enum calss colorb:long long{yellow,black};
2.5 域
引⼊了域,要通过域运算符访问,不可以直接通过枚举体成员名来访问(所以我们可以定义相同的枚举体成员⽽不会发⽣重定义的错误)#include <iostream>
enum class color { red, green, yellow};
enum class colorX { red, green, yellow };
int main() {
//使⽤域运算符访问枚举体成员,强转后打印
std::cout << static_cast<int>(color::red) << std::endl;
std::cout << static_cast<int>(colorX::red) << std::endl;
std::();
return 0;
}
运⾏结果:
3. C++11enum的⼀些新特点
枚举体的定义和声明问题
⽤enum定义的枚举体是⼀个不具有封装性(不知道如何翻译是好:unscoped enumeration)的枚举体,他的成员可以在enum的⼤括号外被直接访问。⽽⽤enum class或是enum struct(⼆者在语法上是等价的)定义的枚举体是具有封装性的(scoped
enumeration),他的成员同过成员名直接访问,⽽应通过域运算符来访问。
#include <iostream>
enum class color{red,black};
enum colorx{green,yellow};
int main() {
color::red;//⽤域运算符访问color的成员
green;//直接访问colorx的成员
colorx::green;//⽤域运算符访问colorx的成员
std::();
return 0;
}
图中的enum-base应该只能是整形的数据(不能是浮点类型或是其他类型),const或是volatile会被忽略。
enumerator-list中的成员被作为常量使⽤,与常量的功能等价。
使⽤=给成员初始化的时候,=右边应该使⽤常量,这个常量应该为整形或是其他的枚举类型。如果第⼀个枚举成员没有初始化,那么