C++11的enum class & enum struct和enumios
C++标准文档——n2347(学习笔记)
连接:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf程序员
问题 | 描述 |
---|---|
1 | 向整形的隐式转换(Implicit conversion to an integer) |
2 | 没法指定底层所使用的数据类型(Inability to specify underlying type) |
3 | enum的做用域(Scope) |
4 | 不一样编译器解决该问题的方法不统一 |
在开始这个问题以前,咱们须要知道什么是整形提高编程
查看以前的博文:C\C++中的整形提高安全
在看完什么是整形提高以后,咱们开始这个问题:markdown
旧版enum其实并不具备很是彻底的类型安全(固然它也体现了必定的类型安全:1.禁止不一样枚举体之间的赋值 2.禁止整形向枚举体的隐式转换等),也就是面对整形提高,旧版enum是没有抗拒力的。less
例如:函数
#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::cin.get(); return 0; }
运行结果:学习
true
test calledui
就像上面的代码:咱们仍然能够比较两个不一样枚举体的大小,用枚举体调用参数为int的函数。显然此时的枚举体发生了 整形提高 。this
在没法使用C++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::cin.get(); return 0; }
的确,封装在类中的enum可以抵抗整形提高。可是这种enum不一样于POD(plain old data),没法放入寄存器当中,这会带来额外的开销。
A. 首先,没法指定数据类型,致使咱们没法明确枚举类型所占的内存大小。这种麻烦在结构体当中尤其突出,特别是当咱们须要内存对齐和填充处理的时候。
#include <iostream> enum Version { Ver1 = 1, Ver2, Ver3 }; struct MyStruct { MyStruct(Version ver) { this->Ver = ver; } Version Ver; //Ohters... }; int main() { MyStruct m(Version::Ver1); std::cin.get(); 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类型 //Ohters... }; int main() { MyStruct m(Version::Ver1); std::cin.get(); 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::cin.get(); 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; }
enum的中的 ” { } ” 大括号并无将枚举成员的可见域限制在大括号内,致使enum成员曝露到了上一级做用域(块语句)中。
例如:
#include <iostream> enum color{red,blue};//定义拥有两个成员的enum,red和blue在enum的大括号外部能够直接访问,而不须要使用域运算符。 int main() { std::cout << blue << std::endl; std::cin.get(); 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::cin.get(); return 0; }
运行结果:
0
1
false
-
是的,只要利用命名空间咱们就能解决枚举体的成员重定义问题,可是添加了多余的一层命名空间,未免显得麻烦
在1.2中展现的图片告诉咱们:有些编译器可能提供了相应的扩展来解决这些问题,可是有的编译器却没有,这使得咱们的编程很是的不统一,有时候由于enum而削弱了程序的可移植性。
如大标题,枚举体的声明和定义使用 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};
//....
与整形之间不会发生隐式类型转换,可是能够强转。
#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::cin.get(); return 0; }
默认的底层数据类型是int,用户能够经过:type(冒号+类型)来指定任何整形(除了wchar_t)做为底层数据类型。
enum class color:unsigned char{red,blue}; enum calss colorb:long long{yellow,black};
引入了域,要经过域运算符访问,不能够直接经过枚举体成员名来访问(因此咱们能够定义相同的枚举体成员而不会发生重定义的错误)
#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::cin.get(); return 0; }
运行结果:
0
0
#include <iostream> enum class color{red,black}; enum colorx{green,yellow}; int main() { color::red;//用域运算符访问color的成员 green;//直接访问colorx的成员 colorx::green;//用域运算符访问colorx的成员 std::cin.get(); return 0; }
1enum color { red=3, black, gray };//成员的值分别为:3 4 5 enum colorx { green, yellow };//成员的值分别为:0 1 enum colorxx{xred,xyellow,xblack=12,xgray};//成员的值分别为:0 1 12 13
enum colora{red};//colora的类型与colorb的类型不一样 enum colorb{yellow};
每种枚举都具备底层数据类型,同过:type(冒号+类型)来指定。对于指定了数据类型的枚举体,他的数据类型为指定的数据类型。若是没有固定的底层数据类型:
- 对于enum class和enum struct来讲,他的底层数据类型是int。
- 对于enum来讲,他的底层数据类型根据编译器而不一样。
- 若是有使用数据初始化,那么他的数据类型与用来初始化的数据的类型相同。
若是该枚举体没有指定的底层数据类型,并且该枚举体的成员为空,那么这个枚举体至关于只有一个成员0
#include <iostream> enum color { red, green, yellow }; int main() { std::cout << std::boolalpha << (red == green) << std::endl;//(red == green)发生了整形提高 std::cin.get(); return 0; }
#include <iostream> enum color { red, green, yellow }; int main() { //color col = 2;//ERROR , “初始化”: 没法从“int”转换为“color” int i = green;//发生隐式转换 std::cout << i << std::endl; std::cin.get(); return 0; }
运行结果:
1
#include <iostream> enum class color { red, green, yellow,a,b,v }; int main() { int res(0); //res = color::red + color::green;//ERROR , “color”不定义该运算符或到预约义运算符可接收的类型的转换 res = static_cast<int>(color::red) + static_cast<int>(color::green); std::cout << res << std::endl; std::cin.get(); return 0; }
运行结果:
1