简体   繁体   English

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

[英]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

  1. enum class es enum class
  2. Plain enum s普通enum

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本地的,它们的值不会隐式转换为其他类型(如另一个enumint

  • 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,因为它们会减少可能导致错误的意外。

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 charintshort ,这些类型中的任何一个都很大足以适合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 可以为enumenum 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成为更好的选择?:

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

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

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM