为什么枚举类优于纯枚举?


429

我听说有人建议使用C ++中的枚举,因为它们具有类型安全性

但这到底是什么意思?


57
当有人声称某种编程构造是“邪恶的”时,他们试图阻止您为自己思考。
皮特·贝克尔

3
@NicolBolas:这更多是一个常见问题,可以提供FAQ答案(是否真的是“ 常见问题”是另外一个故事)。
大卫·罗德里格斯(DavidRodríguez)-dribeas

@David,正在讨论是否应该是FAQ,从这里开始。欢迎输入。
2013年

17
@PeteBecker有时他们只是想保护您免受自己的伤害
piccy

geeksforgeeks.org/…这也是了解enumvs 的好地方enum class
mr_azad

Answers:


472

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-枚举器名称是枚举的局部名称,其值不会隐式转换为其他类型(例如another 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 class应该首选es,因为它们会引起较少的意外,而这些意外可能会导致错误。


7
很好的例子...有没有办法将类版本的类型安全与枚举版本的名称空间提升结合起来?就是说,如果我有一个A带状态的类,并且创建了enum class State { online, offline };一个class的子类A,我想state == online在... A而不是state == State::online... 内部进行检查吗?
标记

31
不。命名空间升级是Bad Thing™,enum class消除它的理由是一半。
2013年

10
在C ++ 11中,您也可以使用显式类型的枚举,例如枚举Animal:unsigned int {dog,deer,cat,bird}
Blasius Secundus

3
@Cat Plus Plus我知道@Oleksiy说这很糟糕。我的问题不是Oleksiy是否认为这很糟糕。我的问题是要求详细说明它的不利之处。具体来说,例如,为什么 Oleksiy会考虑不好Color color = Color::red
chux-恢复莫妮卡

9
@Cat另外加上所以在这个例子的不会发生,直到if (color == Card::red_card)线,4号线在征询意见后(我现在看到适用于该块的前半部分。)2行块给出了不好的例子。前三行不是问题。“整个块是为什么普通枚举不好的原因”使我感到恼火,因为我认为您也认为这些也有问题。我现在看到的只是一个设置。无论如何,感谢您的反馈。
chux-恢复莫妮卡

248

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

enum classES(“新枚举”,“强枚举”)地址三个问题与传统的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 char or int或or short,这些类型中的任何一个都足以容纳所有在中看到的值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可以为enum和指定基础类型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
};

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

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

那么,为什么枚举类比普通枚举更受欢迎?,如果我们可以为scoped(enum class)和unscoped(enum)选择基础类型,那么枚举还有什么enum class更好的选择?

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

1
我想我们也可以将枚举基类型也限制为常规枚举,只要我们拥有C ++ 11
Sagar Padhye

11
抱歉,这个答案是错误的。“枚举类”与指定类型的能力无关。这是常规枚举和枚举类都存在的独立功能。
rdb

14
达成协议:*枚举类是C ++ 11中的新功能。*类型枚举是C ++ 11中的新功能。这是C ++ 11中两个独立的不相关的新功能。可以同时使用,也可以不使用。
rdb

2
我认为Alex Allain提供了我迄今为止在[ cprogramming.com/c++11/…]博客中看到的最完整的简单解释。传统的枚举非常适合使用名称而不是整数值,并且避免使用预处理器#defines,这是一件好事-它增加了清晰度。 枚举类删除了枚举器的数值的概念,并引入了范围和强类型,这增加了(当然,可以增加:-)程序的正确性。它使您更进一步地思考面向对象。
乔恩·斯潘塞

2
顺便说一句,当您查看代码时突然发生一件一件事情总是很有趣。
贾斯汀时间-恢复莫妮卡

47

与普通枚举相比,使用枚举类的基本优点是您可以为两个不同的枚举使用相同的枚举变量,并且仍然可以解析它们(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??)

1
@Oleksiy哦,我没有正确阅读您的问题。对于那些不认识的人,可以考虑将其作为附件。
萨克斯姆(Saksham)2013年

没关系!我几乎忘记了这一点
Oleksiy

当然,您可以编写enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE },轻松解决名称空间问题。namespace参数是我根本不购买的三个参数之一。
乔·

2
@Jo So该解决方案是不必要的解决方法。Enum:enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }与Enum类可比enum class Color1 { RED, GREEN, BLUE }。访问是类似的:COLOR1_REDvs Color1::RED,但是Enum版本要求您在每个值中键入“ COLOR1”,这为错别字提供了更多空间,而枚举类的名称空间行为避免了这种错字。
cdgraham

2
请使用建设性的批评。当我为错别字说更多的空间时,我的意思是当您最初定义的值时enum Color1,编译器无法捕获的值,因为它可能仍然是“有效”名称。如果我使用enum类编写REDGREEN依此类推,那么它将无法解析,enum Banana因为需要您指定Color1::RED才能访问该值(namespace参数)。仍然有使用的好时机enum,但是an的名称空间行为enum class通常会非常有益。
cdgraham

20

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

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

7

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

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

// 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也允许键入 “非类”枚举。名称空间污染问题等仍然存在。看一看在这个问题上已经存在很长时间的相关答案了
。–

7
  1. 不要隐式转换为int
  2. 可以选择哪种类型
  3. ENUM名称空间,避免污染
  4. 与普通类相比,可以声明为正向,但是没有方法

2

值得一提的是,除了这些其他答案之外,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


1

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

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

2
为了完成我之前的评论,请注意,gcc现在有一个名为-Wint-in-bool-context的警告,它将准确地捕获此类错误。
Arnaud

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
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.