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

Why is enum class preferred over plain enum?

提问人:Oleksiy 提问时间:8/20/2013 最后编辑:CommunityOleksiy 更新时间:7/18/2022 访问量:481482

问:

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

但这到底意味着什么?

C 枚举 C++-FAQ

评论

119赞 Pete Becker 8/20/2013
当有人声称某些编程结构是“邪恶的”时,他们试图阻止你自己思考。
5赞 David Rodríguez - dribeas 8/20/2013
@NicolBolas:这更像是一个提供FAQ答案的反问性问题(这是否真的是Frequenty是另一回事)。
1赞 sbi 8/21/2013
@David,有一个讨论是否应该成为常见问题解答,从这里开始。欢迎输入。
40赞 piccy 5/12/2018
@PeteBecker 有时他们只是想保护你免受自己的伤害。
1赞 raz 4/20/2020
geeksforgeeks.org/......这也是一个了解 vs 的好地方。enumenum class

答:

790赞 Oleksiy 8/20/2013 #1

C++ 有两种:enum

  1. enum classES系列
  2. 平原 senum

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

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

两者有什么区别?

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

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

}

结论:

enum class应首选 ES,因为它们引起的意外较少,可能导致错误。

评论

13赞 mark 8/20/2013
好例子...有没有办法将类版本的类型安全与枚举版本的命名空间提升结合起来?也就是说,如果我有一个带有状态的类,并且我创建了一个作为类的孩子,我想在里面做检查,而不是......这可能吗?Aenum class State { online, offline };Astate == onlineAstate == State::online
56赞 Puppy 8/20/2013
不。命名空间提升是一件坏事™,一半的理由是消除它。enum class
27赞 Blasius Secundus 8/20/2013
在 C++11 中,您也可以使用显式类型的枚举,例如枚举 Animal: unsigned int {dog, deer, cat, bird}
8赞 chux - Reinstate Monica 8/22/2013
@Cat Plus Plus 我知道@Oleksiy说这很糟糕。我的问题不是奥列克西是否认为这很糟糕。我的问题是要求详细说明它的坏。具体来说,例如,为什么 Oleksiy 认为 .Color color = Color::red
11赞 chux - Reinstate Monica 8/22/2013
@Cat Plus Plus 因此,该示例的坏示例直到该行才发生,比注释晚 4 行(我现在看到这适用于块的前半部分)。 块的 2 行给出了坏示例。前 3 行不是问题。“整个块是为什么普通枚举不好”让我感到困惑,因为我认为你的意思是这些枚举也有问题。我现在明白了,这只是一个设置。无论如何,感谢您的反馈。if (color == Card::red_card)
64赞 Saksham 8/20/2013 #2

与普通枚举相比,使用枚举类的基本优点是,您可能对 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 };

至于基本枚举,编译器将无法区分是引用类型还是如下语句所示。redColor1Color2

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

评论

2赞 Saksham 8/20/2013
@Oleksiy哦,我没有正确阅读你的问题。考虑是那些不知道的人的附加组件。
1赞 Jo So 10/27/2018
当然,你会写 ,轻松避免命名空间问题。命名空间参数是我这里提到的三个参数之一,我根本不买。enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
3赞 cdgraham 3/6/2019
@Jo 因此,该解决方案是一种不必要的解决方法。枚举:与枚举类:相当。访问类似于: vs ,但枚举版本要求您在每个值中键入“COLOR1”,这为拼写错误提供了更多空间,而枚举类的命名空间行为避免了拼写错误。enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }enum class Color1 { RED, GREEN, BLUE }COLOR1_REDColor1::RED
0赞 Jo So 3/7/2019
which gives more room for typos.对不起,这是我在 2019 年听到的最糟糕的扯头发的论点。是时候练习一下,感受一下重要的事情了。我向你保证,人类在解析标识符方面并不像编译器那样快。所以最好不要挑战他们去做。这是一个令人信服的论点。
4赞 cdgraham 3/7/2019
请使用建设性的批评。当我说有更多错别字的空间时,我的意思是当你最初定义 的值时,编译器无法捕获这些值,因为它可能仍然是一个“有效”的名称。如果我使用枚举类编写等,则它无法解析为,因为它需要您指定才能访问该值(命名空间参数)。仍然有很好的使用时机,但命名空间的行为通常非常有益。enum Color1REDGREENenum BananaColor1::REDenumenum class
324赞 PaperBirdMaster 8/20/2013 #3

来自 Bjarne Stroustrup 的 C++11 FAQ

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

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

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

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

“经典”的基础类型应为大到足以拟合 ;这通常是一个 .此外,每个枚举类型都应与有符号/无符号整数类型兼容。enumenumintchar

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

例如,我已经看到了很多次这样的代码:

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

在上面的代码中,一些幼稚的编码人员认为编译器会将值存储到无符号的 8 位类型中......但是没有关于它的保证:编译器可以选择 or or ,这些类型中的任何一个都足够大,可以容纳 .添加字段是一种负担,不会强制编译器对 .E_MY_FAVOURITE_FRUITSunsigned charintshortenumE_MY_FAVOURITE_FRUITS_FORCE8enum

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

更糟糕的是,如果某个同事不小心为我们的enum

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

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

由于 C++11 可以指定 和 的基础类型(感谢 rdb),因此这个问题得到了巧妙的解决:enumenum class

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()和unscoped()枚举的基础类型,还有什么更好的选择呢?enum classenumenum class

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

评论

24赞 rdb 5/6/2016
对不起,这个答案是错误的。“枚举类”与指定类型的能力无关。这是一个独立的功能,既存在于常规枚举中,也存在于枚举类中。
23赞 rdb 5/6/2016
这是交易: * 枚举类是 C++11 中的一项新功能。* 类型化枚举是 C++11 中的一项新功能。这是 C++11 中两个独立的不相关的新功能。您可以同时使用两者,也可以使用其中之一,或者两者都不使用。
3赞 Jon Spencer 1/7/2017
我认为亚历克斯·阿兰(Alex Allain)提供了我在这个博客中看到的最完整的简单解释,网址为[cprogramming.com/c++11/...。传统的枚举适合使用名称而不是整数值,并避免使用预处理器 #defines,这是一件好事 - 它增加了清晰度。枚举类删除了枚举器数值的概念,并引入了范围和强类型,这会增加(嗯,可以增加:-)程序正确性。它使你离面向对象的思维更近了一步。
2赞 user2672165 4/5/2018
但是,我相信您必须自己定义按位操作,这会增加很多样板代码。
2赞 Justin Time - Reinstate Monica 9/26/2019
顺便说一句,当你在审查代码时,突然发生了海贼王,这总是很有趣。
25赞 Alok151290 2/4/2015 #4

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

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

例如:

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
12赞 Swapnil 8/2/2018 #5

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

评论

1赞 user2864740 2/18/2020
C++11 也允许类型化“非类”枚举。命名空间污染问题等仍然存在。看看在此之前很久就存在的相关答案。
3赞 Arnaud 10/19/2018 #6

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

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

评论

3赞 Arnaud 1/4/2019
为了完成我之前的评论,请注意 gcc 现在有一个名为 -Wint-in-bool-context 的警告,它将准确地捕获此类错误。
5赞 Miro Kropacek 5/13/2019 #7

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

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
};
11赞 Qinsheng Zhang 6/6/2019 #8
  1. 不要隐式转换为 int
  2. 可以选择底层的类型
  3. ENUM 命名空间以避免污染发生
  4. 与普通类相比,可以声明前向,但没有方法
21赞 themeeman 5/14/2020 #9

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

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

与普通变体相比,这很详细,在普通变体中,名称在全局范围内,因此不需要以 . 为前缀。enumColor::

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

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

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