我听说有人建议使用C ++中的枚举类,因为它们具有类型安全性。
但这到底是什么意思?
我听说有人建议使用C ++中的枚举类,因为它们具有类型安全性。
但这到底是什么意思?
Answers:
C ++有两种enum
:
enum class
esenum
小号以下是几个如何声明它们的示例:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
两者有什么区别?
enum class
es-枚举器名称是枚举的局部名称,其值不会隐式转换为其他类型(例如another enum
或int
)
Plain enum
s-枚举器名称与枚举在同一范围内,其值隐式转换为整数和其他类型
例:
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,因为它们会引起较少的意外,而这些意外可能会导致错误。
A
带状态的类,并且创建了enum class State { online, offline };
一个class的子类A
,我想state == online
在... A
而不是state == State::online
... 内部进行检查吗?
enum class
消除它的理由是一半。
Color color = Color::red
。
if (color == Card::red_card)
线,4号线在征询意见后(我现在看到适用于该块的前半部分。)2行块给出了不好的例子。前三行不是问题。“整个块是为什么普通枚举不好的原因”使我感到恼火,因为我认为您也认为这些也有问题。我现在看到的只是一个设置。无论如何,感谢您的反馈。
来自Bjarne Stroustrup的C ++ 11常见问题解答:
在
enum class
ES(“新枚举”,“强枚举”)地址三个问题与传统的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
。与普通枚举相比,使用枚举类的基本优点是您可以为两个不同的枚举使用相同的枚举变量,并且仍然可以解析它们(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 }
,轻松解决名称空间问题。namespace参数是我根本不购买的三个参数之一。
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
与Enum类可比enum class Color1 { RED, GREEN, BLUE }
。访问是类似的:COLOR1_RED
vs Color1::RED
,但是Enum版本要求您在每个值中键入“ COLOR1”,这为错别字提供了更多空间,而枚举类的名称空间行为避免了这种错字。
枚举用于表示一组整数值。
在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
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 ++ 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
。
尚未明确提及的一件事-作用域功能使您可以选择为枚举和类方法使用相同的名称。例如:
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
};