【问题标题】:Why is enum class preferred over plain enum?为什么枚举类比普通枚举更受欢迎?
【发布时间】:2013-08-22 13:14:59
【问题描述】:

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

但这究竟意味着什么?

【问题讨论】:

  • 当有人声称某些编程结构是“邪恶的”时,他们试图阻止您自己思考。
  • @NicolBolas:这更像是一个提供常见问题解答的反问问题(这是否真正频繁被问到是另一回事)。
  • @David,有一个从here开始的讨论是否应该成为一个常见问题解答。欢迎输入。
  • @PeteBecker 有时他们只是想保护你自己。
  • geeksforgeeks.org/… 这也是了解enum vs enum class 的好地方。

标签: c++ class enums c++-faq


【解决方案1】:

C++有两种enum

  1. enum classes
  2. 普通enums

这里有几个关于如何声明它们的示例:

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

两者有什么区别?

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

  • Plain enums - 其中枚举器名称与枚举及其所在的范围相同 值隐式转换为整数和其他类型

例子:

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 classes 应该是首选,因为它们会减少可能导致错误的意外。

【讨论】:

  • 好例子...有没有办法将类版本的类型安全与枚举版本的命名空间提升结合起来?也就是说,如果我有一个带有状态的 A 类,并且我创建了一个 enum class State { online, offline }; 作为 A 类的子类,我想做 state == online 检查 A 而不是 state == State::online ……这可能吗?
  • 不。命名空间提升是一件坏事™,enum class 的一半理由是消除它。
  • 在 C++11 中,您也可以使用显式类型的枚举,例如 enum Animal: unsigned int {dog, deer, cat, bird}
  • @Cat Plus Plus 我知道@Oleksiy 说这很糟糕。我的问题不是 Oleksiy 是否认为这很糟糕。我的问题是要求详细说明 what 的坏处。具体来说,例如,为什么 Oleksiy 认为Color color = Color::red 不好。
  • @Cat Plus Plus 所以示例的 bad 直到 if (color == Card::red_card) 行才出现,比评论晚 4 行(我现在看到的适用于块。)该块的 2 行给出了 bad 示例。前 3 行没有问题。 “整个问题就是普通枚举不好的原因”让我感到震惊,因为我认为您的意思是那些也有问题。我现在明白了,这只是一个设置。无论如何,感谢您的反馈。
【解决方案2】:

来自Bjarne Stroustrup's C++11 FAQ

enum classes("new enums", "strong enums") 解决三个问题 使用传统的 C++ 枚举:

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

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

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

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

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

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

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 值存储为无符号 8 位类型...但对此没有任何保证:编译器可能会选择 unsigned charintshort,这些类型中的任何一个都足够大以适应enum 中看到的所有值。添加字段E_MY_FAVOURITE_FRUITS_FORCE8 是一种负担,不会强制编译器对enum 的底层类型做出任何选择。

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

更糟糕的是,如果某个同事不小心为我们的enum 添加了新值:

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

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

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

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

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

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

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

【讨论】:

  • 我想我们也可以限制常规枚举的枚举基类型,只要我们有 C++11
  • 对不起,这个答案是错误的。 “枚举类”与指定类型的能力无关。这是常规枚举和枚举类都存在的独立功能。
  • 这是交易: * 枚举类是 C++11 中的一个新特性。 * 类型化枚举是 C++11 中的一个新特性。这是 C++11 中两个独立的不相关的新特性。你可以同时使用,也可以使用其中一个,或者都不使用。
  • 我认为 Alex Alllain 提供了我在此博客 [cprogramming.com/c++11/…] 中看到的最完整的简单解释。传统的 enum 有利于使用名称而不是整数值并避免使用预处理器#defines,这是一件好事 - 它增加了清晰度。 enum class 删除了枚举数的数值概念,并引入了范围和强类型,这增加了(嗯,可以增加 :-) 程序的正确性。它让您更接近于思考面向对象。
  • 顺便说一句,当您审查代码并突然 One Piece 发生时,这总是很有趣。
【解决方案3】:

与普通枚举相比,使用枚举类的基本优势在于,您可以为 2 个不同的枚举拥有相同的枚举变量,并且仍然可以解析它们(OP 已将其称为 type safe

例如:

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 类型,如下面的声明。

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

【讨论】:

  • @Oleksiy 哦,我没有正确阅读您的问题。考虑是为那些不知道的人添加的。
  • 没关系!我差点忘了这个
  • 当然,你会写enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE },很容易避免命名空间问题。命名空间参数是这里提到的三个我根本不买的一个。
  • @Jo 所以该解决方案是一种不必要的解决方法。枚举:enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE } 相当于枚举类:enum class Color1 { RED, GREEN, BLUE }。访问方式类似:COLOR1_REDColor1::RED,但 Enum 版本要求您在每个值中键入“COLOR1”,这为拼写错误提供了更多空间,而枚举类的命名空间行为可以避免这种情况。
  • 请使用constructive criticism。当我说有更多的错别字空间时,我的意思是当您最初定义 enum Color1 的值时,编译器无法捕捉到它,因为它可能仍然是一个“有效”名称。如果我使用枚举类编写REDGREEN 等,则它无法解析为enum Banana,因为它需要您指定Color1::RED 才能访问该值(命名空间参数)。使用enum 的时机仍然很好,但enum class 的命名空间行为通常会非常有益。
【解决方案4】:

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

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

例如:

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

这里我们不能混合 Animal 和 Pets 值。

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

【讨论】:

    【解决方案5】:

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

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

    与普通的enum 变体相比,这是冗长的,其中名称在全局范围内,因此不需要以Color:: 为前缀。

    但是,在 C++20 中,我们可以使用 using enum 将枚举中的所有名称引入当前作用域,从而解决问题。

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

    所以现在,没有理由不使用enum class

    【讨论】:

      【解决方案6】:
      1. 不要隐式转换为 int
      2. 可以选择底层的类型
      3. 枚举命名空间以避免发生污染
      4. 与普通类相比,可以前向声明,但没有方法

      【讨论】:

        【解决方案7】:

        C++11 FAQ 提到以下几点:

        常规枚举隐式转换为 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;
        }
        

        常规枚举将其枚举数导出到周围范围,导致名称冲突。

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

        无法指定枚举的底层类型,导致混淆、兼容性问题,并且无法进行前向声明。

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

        【讨论】:

        • C++11 也允许对“非类”枚举进行类型化。命名空间污染等问题依然存在。看看在此之前很久就存在的相关答案..
        【解决方案8】:

        因为,正如在其他答案中所说,类枚举不能隐式转换为 int/bool,它也有助于避免错误代码,例如:

        enum MyEnum {
          Value1,
          Value2,
        };
        ...
        if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
        

        【讨论】:

        • 要完成我之前的评论,请注意 gcc 现在有一个名为 -Wint-in-bool-context 的警告,它将准确捕获此类错误。
        【解决方案9】:

        没有明确提到的一件事 - 范围功能为您提供了一个选项,可以为枚举和类方法使用相同的名称。例如:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-12-16
          • 1970-01-01
          • 1970-01-01
          • 2016-11-26
          • 1970-01-01
          • 1970-01-01
          • 2019-06-11
          • 2016-11-30
          相关资源
          最近更新 更多