为什么枚举类优于普通枚举?

Ole*_*siy 376 c++ enums class c++-faq

我听说有些人因为类型安全而建议在C++中使用枚举.

但这究竟意味着什么?

Ole*_*siy 420

C++有两种enum:

  1. enum classES
  2. 平原enum小号

以下是如何声明它们的几个示例:

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 
Run Code Online (Sandbox Code Playgroud)

两个有什么区别?

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

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

}
Run Code Online (Sandbox Code Playgroud)

结论:

enum classes应该是首选,因为它们可以减少可能导致错误的意外.

  • 不.名称空间促销是Bad Thing™,"enum class"的一半理由是消除它. (26认同)
  • @Cat Plus Plus所以示例的_bad_直到`if(color == Card :: red_card)`行,比注释晚4行(我现在看到它适用于块的前半部分)才会出现.2行该块给出了_bad_示例.前3行不是问题."整个块是为什么简单的枚举很糟糕"让我感到害怕,因为我认为你的意思也是错误的.我现在看到,这只是一个设置.无论如何,感谢您的反馈. (9认同)
  • 在C++ 11中,您也可以使用显式类型的枚举,例如enum Animal:unsigned int {dog,deer,cat,bird} (8认同)
  • 很好的例子......有没有办法将类版本的类型安全性与枚举版本的名称空间促销相结合?也就是说,如果我有一个带有状态的类'A`,并且我创建一个`enum class State {online,offline};`作为类'A`的孩子,我想做`state == online`检查里面的`A`而不是`state == State :: online` ......这可能吗? (6认同)
  • @Cat Plus Plus我知道@Oleksiy说这很糟糕。我的问题不是Oleksiy是否认为这很糟糕。我的问题是要求详细说明_what_是不好的。具体来说,例如,为什么Oleksiy对`Color color = Color :: red`认为不好。 (3认同)
  • @chux:呃,再看看这个评论和"良好使用枚举类(安全)的例子"之间的代码,而不仅仅是第一个语句. (3认同)
  • 为什么“颜色颜色=颜色::红色”是“错误使用普通枚举”的示例?仅仅是因为它使用“普通枚举”还是其他方法,这是不好的吗? (2认同)
  • 另一个没有提到的好处是枚举类可以前向声明。 (2认同)

Pap*_*ter 224

来自Bjarne Stroustrup的C++ 11 FAQ:

enum classES("新枚举","强枚举")解决三个问题,与传统的C++枚举:

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

新的枚举是"枚举类",因为它们将传统枚举(名称值)的各个方面与类的方面(作用域成员和缺少转换)结合起来.

因此,正如其他用户所提到的,"强大的枚举"会使代码更安全.

"经典"的基础类型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?
};
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,一些天真的编码器认为编译器会将E_MY_FAVOURITE_FRUITS值存储到无符号的8位类型......但是对它没有任何保证:编译器可以选择unsigned char或者,int或者short,任何这些类型都足够大以适应所有在...中看到的价值观enum.添加字段E_MY_FAVOURITE_FRUITS_FORCE8是一种负担,并不会强制编译器对基础类型做出任何选择enum.

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

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

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits
Run Code Online (Sandbox Code Playgroud)

编译器不会抱怨它!它只是调整类型的大小以适应所有的值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
};
Run Code Online (Sandbox Code Playgroud)

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

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

那么为什么enum类优于普通枚举?,如果我们可以选择scoped(enum class)和unscoped(enum)枚举的基础类型,还有什么可以做出enum class更好的选择?:

  • 它们不会隐式转换为int.
  • 它们不会污染周围的命名空间.
  • 他们可以向前宣布.

  • 这是交易:*枚举类是C++ 11中的新功能.*键入的枚举是C++ 11中的新功能.这些是C++ 11中两个独立的无关新功能.您可以同时使用两者,也可以两种都使用. (10认同)
  • 对不起,但这个答案是对的."枚举类"与指定类型的能力无关.这是一个独立的功能,既存在于常规枚举,也存在于枚举类中. (7认同)
  • 我认为Alex Allain提供了我在此博客中[http://www.cprogramming.com/c++11/c++11-nullptr-strongly-typed-enum-class时所见的最完整的简单说明。 html]。传统的**枚举**适用于使用名称而不是整数值,并且避免使用预处理器#defines,这是一件好事-它增加了清晰度。**枚举类**删除了枚举器的数值概念,并引入了范围和强类型,这增加了程序的正确性(很好,_can_提高了:-)。它使您更进一步地思考面向对象。 (2认同)
  • 但是,我相信您必须自己定义按位运算,这会添加大量样板代码。 (2认同)
  • 顺便说一句,当你在审查代码时突然发生“海贼王”,这总是很有趣的。 (2认同)

Sak*_*ham 44

使用枚举类优于普通枚举的基本优点是,您可以为2个不同的枚举使用相同的枚举变量,并且仍然可以解析它们(OP 已将其称为类型安全)

例如:

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 };
Run Code Online (Sandbox Code Playgroud)

至于基本枚举,编译器将无法区分red是引用类型Color1还是Color2在hte下面的语句中.

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

  • 请使用[建设性批评](https://stackoverflow.com/help/privileges/comment)。当我说更多的拼写空间时,我的意思是当您最初定义 `enum Color1` 的值时,编译器无法捕捉到它,因为它可能仍然是一个“有效”名称。如果我使用枚举类编写 `RED`、`GREEN` 等,则它无法解析为 `enum Banana`,因为它需要您指定 `Color1::RED` 才能访问该值(命名空间参数)。仍然有使用 `enum` 的好时机,但是 `enum class` 的命名空间行为通常是非常有益的。 (3认同)
  • @Oleksiy 哦,我没有正确阅读您的问题。考虑是那些不知道的人的附加组件。 (2认同)
  • @Jo 所以那个解决方案是一种不必要的解决方法。枚举:`enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }` 相当于枚举类:`enum class Color1 { RED, GREEN, BLUE }`。访问是类似的:`COLOR1_RED` 与 `Color1::RED`,但 Enum 版本要求您在每个值中键入“COLOR1”,这为拼写提供了更多空间,这是枚举类的命名空间行为避免的。 (2认同)

小智 19

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

class后关键字enum指定该枚举是强类型和枚举的作用域.这样,enum类可以防止意外误用常量.

例如:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
Run Code Online (Sandbox Code Playgroud)

在这里,我们不能混合动物和宠物的价值观.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal
Run Code Online (Sandbox Code Playgroud)


Tom*_* VH 11

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

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Qin*_*ang 9

  1. 不要隐式转换为 int
  2. 可以选择哪种类型的基础
  3. 避免污染发生的 ENUM 命名空间
  4. 与普通类相比,可以向前声明,但没有方法


Swa*_*nil 6

C ++ 11常见问题解答提到以下几点:

传统的枚举隐式转换为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;
}
Run Code Online (Sandbox Code Playgroud)

传统的枚举将其枚举数导出到周围的范围,从而引起名称冲突。

// 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
};
Run Code Online (Sandbox Code Playgroud)

枚举的基础类型无法指定,从而导致混乱,兼容性问题,并使前向声明变得不可能。

// 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;
}
Run Code Online (Sandbox Code Playgroud)

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
    PORT_1 = 0x01,
    PORT_2 = 0x02,
    PORT_3 = 0x04
};
Run Code Online (Sandbox Code Playgroud)

// Source.cpp
#include "Header1.h"
#include "Header.h"

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Mir*_*cek 5

尚未明确提及的一件事 - 作用域功能为您提供了一个选项,使枚举和类方法具有相同的名称。例如:

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
};
Run Code Online (Sandbox Code Playgroud)