Why is enum class preferred over plain enum?
我听到有几个人建议在C++中使用EnUM类,因为它们的类型安全。
但这到底是什么意思?
C++有两种类型:0:
下面是几个如何声明它们的示例:
1 2 | enum class Color { red, green, blue }; // enum class enum Animal { dog, cat, bird, human }; // plain enum |
两者有什么区别?
enum class es—枚举器名称是枚举的局部名称,其值不会隐式转换为其他类型(如其他enum 或int )。plain
enum s—其中枚举器名称与枚举及其名称在同一范围内。值隐式转换为整数和其他类型
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | enum Color { red, green, blue }; // plain enum enum Card { red_card, green_card, yellow_card }; // another plain enum enum class Animal { dog, deer, cat, bird, human }; // enum class enum class Mammal { kangaroo, deer, human }; // another enum class void fun() { // examples of bad use of plain enums: Color color = Color::red; Card card = Card::green_card; int num = color; // no problem if (color == Card::red_card) // no problem (bad) cout <<"bad" << endl; if (card == Color::green) // no problem (bad) cout <<"bad" << endl; // examples of good use of enum classes (safe) Animal a = Animal::deer; Mammal m = Mammal::deer; int num2 = a; // error if (m == a) // error (good) cout <<"bad" << endl; if (a == Mammal::deer) // error (good) cout <<"bad" << endl; } |
结论:
应该优先选择EDOCX1[1]es,因为它们会导致更少的可能导致错误的意外。
从Bjarne Stroustrup的C++ 11常见问题:
The
enum class es ("new enums","strong enums") address three problems
with traditional C++ enumerations:
- conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.
- conventional enums export their enumerators to the surrounding scope, causing name clashes.
- the underlying type of an
enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration
impossible.The new enums are"enum class" because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions).
因此,正如其他用户所提到的,"强枚举"将使代码更安全。
"经典"
这是对
例如,我见过很多这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 | enum E_MY_FAVOURITE_FRUITS { E_APPLE = 0x01, E_WATERMELON = 0x02, E_COCONUT = 0x04, E_STRAWBERRY = 0x08, E_CHERRY = 0x10, E_PINEAPPLE = 0x20, E_BANANA = 0x40, E_MANGO = 0x80, E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell? }; |
在上面的代码中,一些天真的编码人员认为编译器会将
如果有一段代码依赖于类型大小和/或假设
更糟的是,如果一些同事不小心给我们的
1 | E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits |
编译器不会抱怨!它只是调整类型的大小以适应EDOCX1的所有值(假设编译器使用的是尽可能小的类型,这是我们无法做到的假设)。对
因为C++ 11有可能为EDCOX1、0和EDCOX1 1的指定基础类型(谢谢RDB),所以这个问题得到了很好的解决:
1 2 3 4 5 6 7 8 9 10 11 12 | enum class E_MY_FAVOURITE_FRUITS : unsigned char { E_APPLE = 0x01, E_WATERMELON = 0x02, E_COCONUT = 0x04, E_STRAWBERRY = 0x08, E_CHERRY = 0x10, E_PINEAPPLE = 0x20, E_BANANA = 0x40, E_MANGO = 0x80, E_DEVIL_FRUIT = 0x100, // Warning!: constant value truncated }; |
指定基础类型如果字段的表达式超出此类型的范围,编译器将抱怨而不是更改基础类型。
我认为这是一个很好的安全改进。
那么,为什么枚举类优先于普通枚举?如果我们可以为作用域(
- 它们不会隐式地转换为
int 。 - 它们不会污染周围的名称空间。
- 它们可以向前声明。
与普通枚举相比,使用枚举类的基本优势在于,对于两个不同的枚举,您可以拥有相同的枚举变量,并且仍然可以解析它们(OP将其称为类型安全的枚举变量)。
例如:
1 2 3 4 5 | enum class Color1 { red, green, blue }; //this will compile enum class Color2 { red, green, blue }; enum Color1 { red, green, blue }; //this will not compile enum Color2 { red, green, blue }; |
对于基本枚举,编译器将无法区分
1 2 3 | enum Color1 { red, green, blue }; enum Color2 { red, green, blue }; int x = red; //Compile time error(which red are you refering to??) |
枚举用于表示一组整数值。
例如:
1 2 | enum class Animal{Dog, Cat, Tiger}; enum class Pets{Dog, Parrot}; |
在这里,我们不能把动物和宠物的价值观混为一谈。
1 2 | Animal a = Dog; // Error: which DOG? Animal a = Pets::Dog // Pets::Dog is not an Animal |
因为,正如在其他答案中所说的,分类ENUM并不意味着可以转换为INT/BOOL,它也有助于避免Buggy Code like:
1 2 3 4 5 6 | enum MyEnum { Value1, Value2, }; ... if (var == Value1 || Value2) // Should be"var == Value2" no error/warning |
C++11 FAQ注释如下:
常规调查隐含地转换为INT,在某人不希望作为一个整体进行调查时造成错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | enum color { Red, Green, Yellow }; enum class NewColor { Red_1, Green_1, Yellow_1 }; int main() { //! Implicit conversion is possible int i = Red; //! Need enum class name followed by access specifier. Ex: NewColor::Red_1 int j = Red_1; // error C2065: 'Red_1': undeclared identifier //! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1; int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int' return 0; } |
Conventional Enums export their enumerators to the surrounding scope,causing name clashes.
ZZU1
一种NENUM的底层类型无法具体化,造成混乱,兼容性问题,并使前进的宣言不可能实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Header1.h #include <iostream> using namespace std; enum class Port : unsigned char; // Forward declare class MyClass { public: void PrintPort(enum class Port p); }; void MyClass::PrintPort(enum class Port p) { cout << (int)p << endl; } |
页:1
1 2 3 4 5 6 7 | // Header.h enum class Port : unsigned char // Declare enum type explicitly { PORT_1 = 0x01, PORT_2 = 0x02, PORT_3 = 0x04 }; |
页:1
1 2 3 4 5 6 7 8 9 10 11 12 | // Source.cpp #include"Header1.h" #include"Header.h" using namespace std; int main() { MyClass m; m.PrintPort(Port::PORT_1); return 0; } |
有一件事没有明确提到——比例尺特征给了你一个选择,给你一个相同的名称和分类方法。For instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Test { public: // these call ProcessCommand() internally void TakeSnapshot(); void RestoreSnapshot(); private: enum class Command // wouldn't be possible without 'class' { TakeSnapshot, RestoreSnapshot }; void ProcessCommand(Command cmd); // signal the other thread or whatever }; |