587

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

但这究竟意味着什么?

4

9 回答 9

637

C++有两种enum

  1. enum classes
  2. 平原enum_

以下是一些关于如何声明它们的示例:

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

两者有什么区别?

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

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

于 2013-08-20T13:06:13.057 回答
289

来自Bjarne Stroustrup 的 C++11 常见问题解答

es( “enum class新枚举”、“强枚举”)解决了传统 C++ 枚举的三个问题:

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

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

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

“经典”的基础类型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 charorintshort,这些类型中的任何一个都足够大以适应所有中看到的值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 可以为enumand指定底层类型enum 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,如果我们可以为 scoped( ) 和 unscoped( ) 枚举选择底层类型,enum还有什么enum class更好的选择?:

  • 它们不会隐式转换为int.
  • 它们不会污染周围的命名空间。
  • 它们可以被前向声明。
于 2013-08-20T15:22:16.710 回答
59

使用枚举类而不是普通枚举的基本优点是,您可能对 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是指类型Color1还是Color2如以下语句中的那样。

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)
于 2013-08-20T13:10:18.597 回答
25

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

后面的class关键字enum指定枚举是强类型的并且它的枚举器是作用域的。这样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
于 2015-02-04T05:01:44.273 回答
13

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

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.

于 2020-05-14T11:00:10.880 回答
9
  1. 不要隐式转换为 int
  2. 可以选择底层的类型
  3. ENUM 命名空间以避免污染发生
  4. 与普通类相比,可以前向声明,但没有方法
于 2019-06-06T02:56:55.833 回答
8

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;
}
于 2018-08-02T12:20:59.710 回答
2

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

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
于 2018-10-19T15:25:47.503 回答
2

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

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
};
于 2019-05-13T07:02:54.290 回答