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