[英]Why is enum class preferred over plain enum?
I heard a few people recommending to use enum classes in C++ because of their type safety .我听说一些人建议在 C++ 中使用枚举类,因为它们的类型安全。
But what does that really mean?但这究竟意味着什么?
C++ has two kinds of enum
: C++ 有两种
enum
:
enum class
es enum class
enum
senum
Here are a couple of examples on how to declare them:以下是一些关于如何声明它们的示例:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
What is the difference between the two?两者有什么区别?
enum class
es - enumerator names are local to the enum and their values do not implicitly convert to other types (like another enum
or int
) enum class
es - 枚举器名称是 enum本地的,它们的值不会隐式转换为其他类型(如另一个enum
或int
)
Plain enum
s - where enumerator names are in the same scope as the enum and their values implicitly convert to integers and other types普通
enum
s - 其中枚举器名称与枚举在同一范围内,并且它们的值隐式转换为整数和其他类型
Example:例子:
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
es should be preferred because they cause fewer surprises that could potentially lead to bugs.应该首选
enum class
es,因为它们会减少可能导致错误的意外。
From Bjarne Stroustrup's C++11 FAQ :来自Bjarne Stroustrup 的 C++11 常见问题解答:
The
enum class
es ("new enums", "strong enums") address three problems with traditional C++ enumerations:enum class
es(“new enums”、“strong enums”)解决了传统 C++ 枚举的三个问题:
- conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.
常规枚举隐式转换为 int,当有人不希望枚举充当整数时会导致错误。
- 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.无法指定
enum
的基础类型,导致混淆、兼容性问题,并使前向声明成为不可能。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).
新的枚举是“枚举类”,因为它们将传统枚举(名称值)的各个方面与类的各个方面(范围成员和没有转换)结合在一起。
So, as mentioned by other users, the "strong enums" would make the code safer.因此,正如其他用户所提到的,“强枚举”将使代码更安全。
The underlying type of a "classic" enum
shall be an integer type large enough to fit all the values of the enum
; “经典”
enum
的基础类型应该是一个足够大的整数类型,以适应enum
的所有值; this is usually an int
.这通常是一个
int
。 Also each enumerated type shall be compatible with char
or a signed/unsigned integer type.此外,每个枚举类型都应与
char
或有符号/无符号整数类型兼容。
This is a wide description of what an enum
underlying type must be, so each compiler will take decisions on its own about the underlying type of the classic enum
and sometimes the result could be surprising.这是对
enum
基础类型必须是什么的广泛描述,因此每个编译器都会自行决定经典enum
的基础类型,有时结果可能会令人惊讶。
For example, I've seen code like this a bunch of times:例如,我见过很多次这样的代码:
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?
};
In the code above, some naive coder is thinking that the compiler will store the E_MY_FAVOURITE_FRUITS
values into an unsigned 8bit type... but there's no warranty about it: the compiler may choose unsigned char
or int
or short
, any of those types are large enough to fit all the values seen in the enum
.在上面的代码中,一些天真的编码人员认为编译器会将
E_MY_FAVOURITE_FRUITS
值存储为无符号 8 位类型......但对此没有任何保证:编译器可能会选择unsigned char
或int
或short
,这些类型中的任何一个都很大足以适合enum
中看到的所有值。 Adding the field E_MY_FAVOURITE_FRUITS_FORCE8
is a burden and doesn't forces the compiler to make any kind of choice about the underlying type of the enum
.添加字段
E_MY_FAVOURITE_FRUITS_FORCE8
是一种负担,不会强制编译器对enum
的基础类型做出任何选择。
If there's some piece of code that rely on the type size and/or assumes that E_MY_FAVOURITE_FRUITS
would be of some width (eg: serialization routines) this code could behave in some weird ways depending on the compiler thoughts.如果有一些代码依赖于类型大小和/或假设
E_MY_FAVOURITE_FRUITS
将具有一定宽度(例如:序列化例程),则此代码可能会以一些奇怪的方式表现,具体取决于编译器的想法。
And to make matters worse, if some workmate adds carelessly a new value to our enum
:更糟糕的是,如果某个同事不小心为我们的
enum
添加了新值:
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
The compiler doesn't complain about it!编译器不会抱怨它! It just resizes the type to fit all the values of the
enum
(assuming that the compiler were using the smallest type possible, which is an assumption that we cannot do).它只是调整类型的大小以适合
enum
的所有值(假设编译器使用了可能的最小类型,这是我们做不到的假设)。 This simple and careless addition to the enum
could subtlety break related code.对
enum
的这种简单粗心的添加可能会巧妙地破坏相关代码。
Since C++11 is possible to specify the underlying type for enum
and enum class
(thanks rdb ) so this issue is neatly addressed:由于 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
};
Specifying the underlying type if a field have an expression out of the range of this type the compiler will complain instead of changing the underlying type.如果字段的表达式超出此类型的范围,则指定基础类型,编译器将抱怨而不是更改基础类型。
I think that this is a good safety improvement.我认为这是一个很好的安全改进。
So Why is enum class preferred over plain enum?那么为什么枚举类比普通枚举更受欢迎? , if we can choose the underlying type for scoped(
enum class
) and unscoped ( enum
) enums what else makes enum class
a better choice?: ,如果我们可以为 scoped(
enum class
) 和 unscoped ( enum
) enums 选择底层类型,还有什么能让enum class
成为更好的选择?:
int
.int
。The basic advantage of using enum class over normal enums is that you may have same enum variables for 2 different enums and still can resolve them(which has been mentioned as type safe by OP)使用枚举类而不是普通枚举的基本优点是,您可能对 2 个不同的枚举具有相同的枚举变量,并且仍然可以解析它们(OP 已将其称为类型安全)
For eg:例如:
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 };
As for the basic enums, compiler will not be able to distinguish whether red
is refering to the type Color1
or Color2
as in hte below statement.至于基本枚举,编译器将无法区分
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??)
Enumerations are used to represent a set of integer values.枚举用于表示一组整数值。
The class
keyword after the enum
specifies that the enumeration is strongly typed and its enumerators are scoped. enum
后面的class
关键字指定枚举是强类型的,并且它的枚举器是作用域的。 This way enum
classes prevents accidental misuse of constants.这样
enum
类可以防止意外误用常量。
For Example:例如:
enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
Here we can not mix Animal and Pets values.这里我们不能混合 Animal 和 Pets 值。
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
It's worth noting, on top of these other answers, that C++20 solves one of the problems that enum class
has: verbosity.值得注意的是,除了这些其他答案之外,C++20 还解决了
enum class
存在的问题之一:冗长。 Imagining a hypothetical enum class
, Color
.想象一个假设的
enum class
Color
。
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
This is verbose compared to the plain enum
variation, where the names are in the global scope and therefore don't need to be prefixed with Color::
.与普通
enum
变体相比,这很冗长,其中名称在全局范围内,因此不需要以Color::
为前缀。
However, in C++20 we can use using enum
to introduce all of the names in an enum to the current scope, solving the problem.但是,在 C++20 中,我们可以使用
using enum
将枚举中的所有名称引入当前作用域,从而解决问题。
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
So now, there is no reason not to use enum class
.所以现在,没有理由不使用
enum class
。
C++11 FAQ mentions below points: C++11 FAQ提到以下几点:
conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.常规枚举隐式转换为 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;
}
conventional enums export their enumerators to the surrounding scope, causing name clashes.常规枚举将其枚举数导出到周围范围,从而导致名称冲突。
// 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
};
The underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible.无法指定枚举的底层类型,导致混淆、兼容性问题,并且无法进行前向声明。
// 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;
}
Because, as said in other answers, class enum are not implicitly convertible to int/bool, it also helps to avoid buggy code like:因为,正如在其他答案中所说,类枚举不能隐式转换为 int/bool,它还有助于避免错误代码,例如:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
One thing that hasn't been explicitly mentioned - the scope feature gives you an option to have the same name for an enum and class method.没有明确提到的一件事 - 范围功能为您提供了一个选项,可以为枚举和类方法使用相同的名称。 For instance:
例如:
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
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.