C++11的enum class & enum struct和enum
C++標准文檔——n2347(學習筆記)
鏈接:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf
1. 舊版enum存在的問題
問題 | 描述 |
---|---|
1 | 向整形的隱式轉換(Implicit conversion to an integer) |
2 | 無法指定底層所使用的數據類型(Inability to specify underlying type) |
3 | enum的作用域(Scope) |
4 | 不同編譯器解決該問題的方法不統一 |
1.1 問題1:向整形的隱式轉換
在開始這個問題之前,我們需要知道什么是整形提升
查看之前的博文:C\C++中的整形提升
在看完什么是整形提升之后,我們開始這個問題:
舊版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::cin.get(); return 0; }
運行結果:
true
test called
就像上面的代碼:我們仍然可以比較兩個不同枚舉體的大小,用枚舉體調用參數為int的函數。顯然此時的枚舉體發生了 整形提升 。
在無法使用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),無法放入寄存器當中,這會帶來額外的開銷。
1.2 問題2:無法指定底層所使用的數據類型
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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
此時我們的解決辦法還是:不使用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; }
1.3 問題3:enum的作用域
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.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::cin.get(); 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::cin.get(); return 0; }
運行結果:
0
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::cin.get(); return 0; }
- 圖中的enum-base應該只能是整形的數據(不能是浮點類型或是其他類型),const或是volatile會被忽略。
- enumerator-list中的成員被作為常量使用,與常量的功能等價。
- 使用=給成員初始化的時候,=右邊應該使用常量,這個常量應該為整形或是其他的枚舉類型。如果第一個枚舉成員沒有初始化,那么他默認為0,其他沒有初始化的成員是前面一個成員的值加1。
enum 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
1
- 每種枚舉體的類型都不同於其他枚舉體。
enum colora{red};//colora的類型與colorb的類型不同 enum colorb{yellow};
-
每種枚舉都具有底層數據類型,同過:type(冒號+類型)來指定。對於指定了數據類型的枚舉體,他的數據類型為指定的數據類型。如果沒有固定的底層數據類型:
- 對於enum class和enum struct來說,他的底層數據類型是int。
- 對於enum來說,他的底層數據類型根據編譯器而不同。
- 如果有使用數據初始化,那么他的數據類型與用來初始化的數據的類型相同。
-
如果該枚舉體沒有指定的底層數據類型,而且該枚舉體的成員為空,那么這個枚舉體相當於只有一個成員0
- enum(非enum class\enum struct)會發生整形提升
#include <iostream> enum color { red, green, yellow }; int main() { std::cout << std::boolalpha << (red == green) << std::endl;//(red == green)發生了整形提升 std::cin.get(); return 0; }
- enum(非enum class\enum struct)會發生自動數據類型轉換。但是enum class\enum struct是不允許這么做的。
#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
- 可以對enum和enum class\enum struct進行強制轉換。
#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
3. 轉載請注明出處
- 頂