本文翻译自: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? 但这到底是什么意思?
#1楼
参考:https://stackoom.com/question/1Evzh/为什么枚举类优于纯枚举
#2楼
C++ has two kinds of enum
: C ++有两种enum
:
-
enum class
esenum class
- Plain
enum
s 普通enum
Here are a couple of examples 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 two? 两者有什么区别?
enum class
es - enumerator names are local to the enum and their values do not implicitly convert to other types (like anotherenum
orint
)enum class
es-枚举器名称是枚举局部的,它们的值不会隐式转换为其他类型(例如另一个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;
}
Conclusion: 结论:
enum class
es should be preferred because they cause fewer surprises that could potentially lead to bugs. enum class
es应该是首选,因为它们会引起较少的意外,而意外可能会导致bug。
#3楼
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) 与普通枚举相比,使用枚举类的基本优点是您可以为两个不同的枚举使用相同的枚举变量,并且仍然可以解析它们(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??)
#4楼
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(“新枚举”,“强枚举”)解决了传统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 his 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
)枚举选择基础类型,那么还有什么使enum class
成为更好的选择呢?
- They don't convert implicitly to
int
. 它们不会隐式转换为int
。 - They don't pollute the surrounding namespace. 它们不会污染周围的名称空间。
- They can be forward-declared. 它们可以被预先声明。
#5楼
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 a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
#6楼
C++11 FAQ mentions below points: C ++ 11常见问题解答提到以下几点:
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;
}