C++ 为什么枚举类比普通枚举更受欢迎?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18335861/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:54:39  来源:igfitidea点击:

Why is enum class preferred over plain enum?

c++classenumsc++-faq

提问by Oleksiy

I heard a few people recommending to use enum classesin C++ because of their type safety.

我听说有些人建议在 C++ 中使用枚举,因为它们的类型安全

But what does that really mean?

但这真的意味着什么?

回答by Oleksiy

C++ has two kinds of enum:

C++有两种enum

  1. enum classes
  2. Plain enums
  1. enum classes
  2. 平原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 classes - enumerator names are localto the enum and their values do notimplicitly convert to other types (like another enumor int)

  • Plain enums - where enumerator names are in the same scope as the enum and their values implicitly convert to integers and other types

  • enum classes - 枚举器名称是枚举的本地名称,它们的值不会隐式转换为其他类型(如 anotherenumint

  • 普通enums - 其中枚举器名称与枚举在同一范围内,并且它们的值隐式转换为整数和其他类型

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 classes should be preferred because they cause fewer surprises that could potentially lead to bugs.

enum classes 应该是首选,因为它们引起的意外较少,可能会导致错误。

回答by PaperBirdMaster

From Bjarne Stroustrup's C++11 FAQ:

来自Bjarne Stroustrup 的 C++11 常见问题解答

The enum classes ("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 enumcannot 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).

enum classES(“新枚举”,“强枚举”)地址三个问题与传统的C ++枚举:

  • 常规枚举隐式转换为 int,当有人不希望枚举充当整数时会导致错误。
  • 常规枚举将其枚举器导出到周围的范围,从而导致名称冲突。
  • enum无法指定an 的底层类型,从而导致混淆、兼容性问题,并且无法进行前向声明。

新的枚举是“枚举类”,因为它们将传统枚举的方面(名称值)与类的方面(范围成员和没有转换)结合在一起。

So, as mentioned by other users, the "strong enums" would make the code safer.

因此,正如其他用户所提到的,“强枚举”将使代码更安全。

The underlying type of a "classic" enumshall be an integer type large enough to fit all the values of the enum; this is usually an int. Also each enumerated type shall be compatible with charor a signed/unsigned integer type.

“经典”的基础类型enum应该是一个足够大的整数类型,以适合所有的值enum;这通常是一个int. 此外,每个枚举类型应与char有符号/无符号整数类型兼容。

This is a wide description of what an enumunderlying type must be, so each compiler will take decisions on his own about the underlying type of the classic enumand 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_FRUITSvalues into an unsigned 8bit type... but there's no warranty about it: the compiler may choose unsigned charor intor short, any of those types are large enough to fit all the values seen in the enum. Adding the field E_MY_FAVOURITE_FRUITS_FORCE8is 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值存储到无符号 8 位类型中……但对此没有任何保证:编译器可能会选择unsigned charorintshort,这些类型中的任何一个都足够大以适合所有中看到的值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_FRUITSwould be of some width (e.g: 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). This simple and careless addition to the enumcould subtlety break related code.

编译器不会抱怨它!它只是调整类型以适应所有值enum(假设编译器使用尽可能小的类型,这是我们无法做到的假设)。这种简单粗心的添加enum可能会微妙地破坏相关代码。

Since C++11 is possible to specify the underlying type for enumand enum class(thanks rdb) so this issue is neatly addressed:

由于 C++11 可以为enumand指定底层类型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 classa better choice?:

那么为什么枚举类比普通枚举更受欢迎?,如果我们可以为 scoped( enum class) 和 unscoped ( enum) 枚举选择底层类型,还有什么enum class更好的选择?:

  • They don't convert implicitly to int.
  • They don't pollute the surrounding namespace.
  • They can be forward-declared.
  • 它们不会隐式转换为int.
  • 它们不会污染周围的命名空间。
  • 它们可以预先声明。

回答by Saksham

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 safeby 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 redis refering to the type Color1or Color2as 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??)

回答by Alok151290

Enumerations are used to represent a set of integer values.

枚举用于表示一组整数值。

The classkeyword after the enumspecifies that the enumeration is strongly typed and its enumerators are scoped. This way enumclasses prevents accidental misuse of constants.

class后关键字enum指定该枚举是强类型和枚举的作用域。通过这种方式,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

回答by Swapnil

C++11 FAQmentions 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;
}

回答by Qinsheng Zhang

  1. do not implicitly convert to int
  2. can choose which type underlie
  3. ENUM namespace to avoid polluting happen
  4. Compared with normal class, can be declared forward, but do not have methods
  1. 不要隐式转换为 int
  2. 可以选择哪种类型的基础
  3. 避免污染发生的 ENUM 命名空间
  4. 与普通类相比,可以向前声明,但没有方法

回答by Arnaud

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

回答by Miro Kropacek

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
};

回答by Tom VH

It's worth noting, on top of these other answers, that C++20 solves one of the problems that enum classhas: verbosity. Imagining a hypothetical enum class, Color.

值得注意的是,除了这些其他答案之外,C++20 解决了其中一个问题enum class:冗长。想象一个假设enum classColor

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

This is verbose compared to the plain enumvariation, 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 enumto 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.