关于c ++:为什么enum类优于普通枚举?

Why is enum class preferred over plain enum?

我听到有几个人建议在C++中使用EnUM类,因为它们的类型安全。

但这到底是什么意思?


C++有两种类型:0:

  • enum classES
  • 平原enums
  • 下面是几个如何声明它们的示例:

    1
    2
     enum class Color { red, green, blue }; // enum class
     enum Animal { dog, cat, bird, human }; // plain enum

    两者有什么区别?

    • enum classes—枚举器名称是枚举的局部名称,其值不会隐式转换为其他类型(如其他enumint)。

    • plain enums—其中枚举器名称与枚举及其名称在同一范围内。值隐式转换为整数和其他类型

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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;

    }

    结论:

    应该优先选择EDOCX1[1]es,因为它们会导致更少的可能导致错误的意外。


    从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 enum cannot 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的底层类型应是一个整数类型,其大小应足以满足enum的所有值;这通常是int类型。此外,每种枚举类型应与char或有符号/无符号整数类型兼容。

    这是对enum基础类型必须是什么的广泛描述,因此每个编译器将自己决定经典enum的基础类型,有时结果可能会令人吃惊。

    例如,我见过很多这样的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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值存储到无符号8bit类型中…但是没有保证:编译器可以选择unsigned charintshort,其中任何一种类型的大小都足以满足enum中看到的所有值。添加字段E_MY_FAVOURITE_FRUITS_FORCE8是一种负担,并不强制编译器对enum的底层类型做出任何选择。

    如果有一段代码依赖于类型大小和/或假设E_MY_FAVOURITE_FRUITS具有一定宽度(例如:序列化例程),则根据编译器的想法,该代码可能以某些奇怪的方式运行。

    更糟的是,如果一些同事不小心给我们的enum增加了新的价值:

    1
        E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

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

    因为C++ 11有可能为EDCOX1、0和EDCOX1 1的指定基础类型(谢谢RDB),所以这个问题得到了很好的解决:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
    };

    指定基础类型如果字段的表达式超出此类型的范围,编译器将抱怨而不是更改基础类型。

    我认为这是一个很好的安全改进。

    那么,为什么枚举类优先于普通枚举?如果我们可以为作用域(enum class和非作用域(enum枚举)选择基础类型,还有什么使enum class成为更好的选择?:

    • 它们不会隐式地转换为int
    • 它们不会污染周围的名称空间。
    • 它们可以向前声明。


    与普通枚举相比,使用枚举类的基本优势在于,对于两个不同的枚举,您可以拥有相同的枚举变量,并且仍然可以解析它们(OP将其称为类型安全的枚举变量)。

    例如:

    1
    2
    3
    4
    5
    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类型。

    1
    2
    3
    enum Color1 { red, green, blue };  
    enum Color2 { red, green, blue };
    int x = red;    //Compile time error(which red are you refering to??)


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

    enum后面的class关键字指定枚举是强类型的,并且枚举器的作用域为作用域。这样,enum类可以防止意外地误用常量。

    例如:

    1
    2
    enum class Animal{Dog, Cat, Tiger};
    enum class Pets{Dog, Parrot};

    在这里,我们不能把动物和宠物的价值观混为一谈。

    1
    2
    Animal a = Dog;       // Error: which DOG?    
    Animal a = Pets::Dog  // Pets::Dog is not an Animal

    因为,正如在其他答案中所说的,分类ENUM并不意味着可以转换为INT/BOOL,它也有助于避免Buggy Code like:

    1
    2
    3
    4
    5
    6
    enum MyEnum {
      Value1,
      Value2,
    };
    ...
    if (var == Value1 || Value2) // Should be"var == Value2" no error/warning


    C++11 FAQ注释如下:

    常规调查隐含地转换为INT,在某人不希望作为一个整体进行调查时造成错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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.

    ZZU1

    一种NENUM的底层类型无法具体化,造成混乱,兼容性问题,并使前进的宣言不可能实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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;
    }

    页:1

    1
    2
    3
    4
    5
    6
    7
    // Header.h
    enum class Port : unsigned char // Declare enum type explicitly
    {
        PORT_1 = 0x01,
        PORT_2 = 0x02,
        PORT_3 = 0x04
    };

    页:1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Source.cpp
    #include"Header1.h"
    #include"Header.h"

    using namespace std;
    int main()
    {
        MyClass m;
        m.PrintPort(Port::PORT_1);

        return 0;
    }


    有一件事没有明确提到——比例尺特征给了你一个选择,给你一个相同的名称和分类方法。For instance:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    };