但是这到底意味着什么呢?
C++有两种枚举类型:enum class
和普通的enum
。
enum class
类型的枚举enum
类型的枚举以下是它们的声明示例:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
这两者之间有什么区别?
enum class
- 枚举器名称是局部的,它们的值不会隐式转换为其他类型(如另一个enum
或int
)
普通的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
应该更受青睐,因为它们会导致更少的意外情况,这些意外情况可能会导致错误。
A
,并且我创建一个作为A
类子元素的enum class State { online, offline };
,我想在A
内部进行state == online
检查,而不是state == State::online
...这可行吗? - markenum class
的其中一项正当理由就是消除它。 - PuppyColor color = Color::red
这样会被Oleksiy认为不好? - chux - Reinstate Monicaif (color == Card::red_card)
这一行出现,比注释晚了4行(我现在看到注释只适用于块的前半部分)。该块的2行给出了“错误”的示例。前3行没有问题。 “整个块为何纯枚举类型不好”让我困惑,因为我以为您也认为它们有问题。 我现在明白了,这只是一个铺垫。无论如何,感谢您的反馈。 - chux - Reinstate Monica
enum class
(“新枚举”,“强枚举”)解决了传统C++枚举的三个问题:
- 传统枚举会隐式转换为int,当某人不想将枚举类型作为整数时会导致错误。
- 传统枚举将其枚举器导出到周围的范围,导致名称冲突。
- 无法指定枚举的基础类型,会引起混乱、兼容性问题,并使前向声明变得不可能。
新的枚举是“枚举类”,因为它们结合了传统枚举(名称值)和类的方面(作用域成员和没有转换)。
因此,正如其他用户所提到的,“强枚举”将使代码更安全。
“经典” 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
或者 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
编译器不会给出错误提示,它只是调整类型的大小以适应所有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
};
如果一个字段有一个超出其范围的表达式,指定底层类型时,编译器会发出警告而不是更改底层类型。
我认为这是一种很好的安全改进。
那么,为什么enum class
比普通枚举更受推崇呢?如果我们可以为作用域为enum class
和非作用域为enum
的枚举选择底层类型,还有什么使enum class
成为更好的选择呢?:
int
。使用枚举类而不是普通枚举的基本优势在于,您可以拥有相同的枚举变量用于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??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
,轻松避免命名空间问题。在这里提到的三个参数中,命名空间参数是我完全不认同的一个。 - Jo Soenum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
可以与 Enum 类相媲美: enum class Color1 { RED, GREEN, BLUE }
。访问方式类似:COLOR1_RED
与Color1::RED
,但是枚举版本需要在每个值中键入“COLOR1”,这给打错字留下了更多空间,而枚举类的命名空间行为避免了这种情况。 - cdgrahamenum Color1
的值时,编译器无法捕获它,因为它仍然可能是一个“有效”的名称。如果我使用枚举类写RED
、GREEN
等,那么它不能解析为enum Banana
,因为它需要您指定Color1::RED
(命名空间参数)才能访问该值。仍然有一些适合使用enum
的场景,但是enum class
的命名空间行为通常非常有益。 - cdgraham枚举类型用于表示一组整数值。
enum
后面的 class
关键字指定了枚举类是强类型的,其枚举器是有作用域的。这样,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
值得注意的是,在这些其他答案之外,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
了。
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;
}
有一件事情还没有明确提到——作用域功能为枚举和类方法提供了相同的名称选项。例如:
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
};
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
enum
和enum class
区别的好地方。https://www.geeksforgeeks.org/enum-classes-in-c-and-their-advantage-over-enum-datatype/ - herr_azad