C++有两种enum
:
枚举类 普通枚举
以下是一些关于如何声明它们的示例:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
两者有什么区别?
枚举类 - 枚举器名称是枚举的本地名称,并且它们的值不会隐式转换为其他类型(如另一个枚举或 int)
普通枚举 - 其中枚举器名称与枚举在同一范围内,并且它们的值隐式转换为整数和其他类型
例子:
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;
}
结论:
enum class
应该首选,因为它们会减少可能导致错误的意外情况。
从 Bjarne Stroustrup's C++11 FAQ:
枚举类(“新枚举”、“强枚举”)解决了传统 C++ 枚举的三个问题:传统枚举隐式转换为 int,当有人不希望枚举充当整数时会导致错误。常规枚举将其枚举数导出到周围范围,从而导致名称冲突。无法指定枚举的基础类型,导致混淆、兼容性问题,并使前向声明成为不可能。新的枚举是“枚举类”,因为它们将传统枚举(名称值)的各个方面与类的各个方面(范围成员和没有转换)结合在一起。
因此,正如其他用户所提到的,“强枚举”将使代码更安全。
“经典”enum
的基础类型应为整数类型,足以容纳 enum
的所有值;这通常是 int
。此外,每个枚举类型都应与 char
或有符号/无符号整数类型兼容。
这是对 enum
基础类型必须是什么的广泛描述,因此每个编译器都会自行决定经典 enum
的基础类型,有时结果可能会令人惊讶。
例如,我见过很多次这样的代码:
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?
};
在上面的代码中,一些天真的编码人员认为编译器会将 E_MY_FAVOURITE_FRUITS
值存储为无符号 8 位类型......但对此没有任何保证:编译器可能会选择 unsigned char
或 int
或 short
, 这些类型中的任何一个都足够大以适应 enum
中看到的所有值。添加字段 E_MY_FAVOURITE_FRUITS_FORCE8
是一种负担,不会强制编译器对 enum
的基础类型做出任何选择。
如果有一些代码依赖于类型大小和/或假设 E_MY_FAVOURITE_FRUITS
将具有一定宽度(例如:序列化例程),则此代码可能会以一些奇怪的方式运行,具体取决于编译器的想法。
更糟糕的是,如果某个同事不小心为我们的 enum
添加了新值:
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
编译器不会抱怨它!它只是调整类型的大小以适应 enum
的所有值(假设编译器使用了可能的最小类型,这是我们做不到的假设)。对 enum
的这种简单粗心的添加可能会巧妙地破坏相关代码。
由于 C++11 可以为 enum
和 enum class
指定底层类型(感谢 rdb),所以这个问题得到了很好的解决:
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
};
如果字段的表达式超出此类型的范围,则指定基础类型,编译器将抱怨而不是更改基础类型。
我认为这是一个很好的安全改进。
那么为什么枚举类比普通枚举更受欢迎?,如果我们可以为 scoped(enum class
) 和 unscoped (enum
) 枚举选择底层类型,还有什么能让 enum class
成为更好的选择? :
它们不会隐式转换为 int。
它们不会污染周围的命名空间。
它们可以被前向声明。
使用枚举类而不是普通枚举的基本优点是,您可能对 2 个不同的枚举具有相同的枚举变量,并且仍然可以解析它们(OP 已将其称为类型安全)
例如:
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 };
至于基本枚举,编译器将无法区分 red
是指类型 Color1
还是 Color2
,如下面的语句所示。
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Compile time error(which red are you refering to??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
,轻松避免命名空间问题。命名空间参数是这里提到的三个我根本不购买的参数之一。
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
与枚举类:enum class Color1 { RED, GREEN, BLUE }
相当。访问类似:COLOR1_RED
与 Color1::RED
,但 Enum 版本要求您在每个值中键入“COLOR1”,这为拼写错误提供了更多空间,而枚举类的命名空间行为可以避免这种情况。
enum Color1
的值时,编译器无法捕捉到它,因为它可能仍然是一个“有效”名称。如果我使用枚举类编写 RED
、GREEN
等,则它无法解析为 enum Banana
,因为它需要您指定 Color1::RED
才能访问值(命名空间参数)。仍然有使用 enum
的好时机,但 enum class
的命名空间行为通常非常有益。
枚举用于表示一组整数值。
enum
之后的 class
关键字指定枚举是强类型的,并且它的枚举数是作用域的。这样 enum
类可以防止意外误用常量。
例如:
enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
这里我们不能混合 Animal 和 Pets 值。
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
值得注意的是,除了这些其他答案之外,C++20 还解决了 enum class
的问题之一:冗长。想象一个假设的 enum class
,Color
。
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
与普通的 enum
变体相比,这很冗长,其中名称在全局范围内,因此不需要以 Color::
为前缀。
但是,在 C++20 中,我们可以使用 using enum
将枚举中的所有名称引入当前作用域,从而解决问题。
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
所以现在,没有理由不使用 enum class
。
不隐式转换为 int 可以选择 ENUM 命名空间下的类型以避免污染发生 与普通类相比,可以向前声明,但没有方法
C++11 FAQ 提到以下几点:
常规枚举隐式转换为 int,当有人不希望枚举充当整数时会导致错误。
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;
}
常规枚举将其枚举数导出到周围范围,从而导致名称冲突。
// Header.h
enum vehicle
{
Car,
Bus,
Bike,
Autorickshow
};
enum FourWheeler
{
Car, // error C2365: 'Car': redefinition; previous definition was 'enumerator'
SmallBus
};
enum class Editor
{
vim,
eclipes,
VisualStudio
};
enum class CppEditor
{
eclipes, // No error of redefinitions
VisualStudio, // No error of redefinitions
QtCreator
};
无法指定枚举的底层类型,导致混淆、兼容性问题,并且无法进行前向声明。
// 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;
}
.
// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
PORT_1 = 0x01,
PORT_2 = 0x02,
PORT_3 = 0x04
};
.
// Source.cpp
#include "Header1.h"
#include "Header.h"
using namespace std;
int main()
{
MyClass m;
m.PrintPort(Port::PORT_1);
return 0;
}
因为,正如在其他答案中所说,类枚举不能隐式转换为 int/bool,它还有助于避免错误代码,例如:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
没有明确提到的一件事 - 范围功能为您提供了一个选项,可以为枚举和类方法使用相同的名称。例如:
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
};
不定期副业成功案例分享
A
,并且我创建了一个enum class State { online, offline };
作为类A
的子类,我想在A
而不是state == State::online
内进行state == online
检查... 那可能吗?enum class
的一半理由是消除它。Color color = Color::red
不好。if (color == Card::red_card)
行才出现,比注释晚 4 行(我现在看到它适用于该块的前半部分。) 2 行块给出了 bad 的例子。前 3 行没有问题。 “整个问题就是普通枚举不好的原因”让我感到震惊,因为我认为您的意思是那些也有问题。我现在明白了,这只是一个设置。无论如何,感谢您的反馈。