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

时间:2013-08-20 13:06:13

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

我听说有些人建议在C ++中使用enum classes ,因为他们的类型安全

但这究竟意味着什么?

9 个答案:

答案 0 :(得分:365)

C ++有两种enum

  1. enum class ES
  2. 普通enum s
  3. 以下是一些如何声明它们的示例:

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

    两者有什么区别?

    • enum class es - 枚举器名称 local 枚举,其值隐式转换为其他类型(如另一个{{1 }}或enum

    • 普通int s - 枚举器名称与枚举的范围相同 值隐式转换为整数和其他类型

    示例:

    enum

    结论:

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

答案 1 :(得分:205)

来自Bjarne Stroustrup's C++11 FAQ

  

enum class es(“新枚举”,“强枚举”)解决了三个问题   使用传统的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类比普通枚举更受欢迎?,如果我们可以选择作用域(enum class)和未作用域(enum)的基础类型,那么还有什么可以使enum class更好的选择?:

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

答案 2 :(得分:40)

使用枚举类优于普通枚举的基本优点是,您可以为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 };

对于基本枚举,编译器将无法区分red是否引用类型Color1Color2,如下面的语句中的hte。

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

答案 3 :(得分:19)

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

class之后的enum关键字指定枚举是强类型的,其枚举数是作用域的。这种enum类可以防止意外误用常量。

例如:

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

这里我们不能混合动物和宠物的价值。

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

答案 4 :(得分:5)

值得注意的是,除了这些其他答案之外,C ++ 20还解决了td[@class='table-cell-md']/a存在的问题之一:冗长。想象一个<td>enum class

enum class

与普通的Color变体相比,这是冗长的,在普通变体中,名称在全局范围内,因此不需要前缀void foo(Color c) switch (c) { case Color::Red: ...; case Color::Green: ...; case Color::Blue: ...; // etc } }

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

Color::

因此,现在没有理由不使用using enum

答案 5 :(得分:2)

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

答案 6 :(得分:2)

因为如其他答案所述,类枚举不能隐式转换为int / bool,这还有助于避免出现如下错误代码:

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

答案 7 :(得分:0)

尚未明确提及的一件事-作用域功能使您可以选择为枚举和类方法使用相同的名称。例如:

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

答案 8 :(得分:0)

  1. 请勿隐式转换为int
  2. 可以选择基础类型
  3. ENUM名称空间以避免污染
  4. 与普通类相比,可以向前声明,但没有方法