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

本文翻译自:Why is enum class preferred over plain enum?

I heard a few people recommending to use enum classes in C++ because of their type safety . 我听说有人建议使用C ++中的枚举 ,因为它们具有类型安全性

But what does that really mean? 但这到底是什么意思?


#1楼

参考:https://stackoom.com/question/1Evzh/为什么枚举类优于纯枚举


#2楼

C++ has two kinds of enum : C ++有两种enum

  1. enum class es enum class
  2. Plain enum s 普通enum

Here are a couple of examples how to declare them: 以下是几个如何声明它们的示例:

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

What is the difference between two? 两者有什么区别?

  • enum class es - enumerator names are local to the enum and their values do not implicitly convert to other types (like another enum or int ) enum class es-枚举器名称是枚举局部的,它们的值不会隐式转换为其他类型(例如另一个enumint

  • Plain enum s - where enumerator names are in the same scope as the enum and their values implicitly convert to integers and other types 普通enum s-枚举器名称与枚举在同一范围内,其值隐式转换为整数和其他类型

Example: 例:

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;

}

Conclusion: 结论:

enum class es should be preferred because they cause fewer surprises that could potentially lead to bugs. enum class es应该是首选,因为它们会引起较少的意外,而意外可能会导致bug。


#3楼

The basic advantage of using enum class over normal enums is that you may have same enum variables for 2 different enums and still can resolve them(which has been mentioned as type safe by OP) 与普通枚举相比,使用枚举类的基本优点是您可以为两个不同的枚举使用相同的枚举变量,并且仍然可以解析它们(OP将该类型称为安全类型

For eg: 例如:

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

As for the basic enums, compiler will not be able to distinguish whether red is refering to the type Color1 or Color2 as in hte below statement. 至于基本枚举,编译器将无法区分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??)

#4楼

From Bjarne Stroustrup's C++11 FAQ : 来自Bjarne Stroustrup的C ++ 11常见问题解答

The enum class es ("new enums", "strong enums") address three problems with traditional C++ enumerations: enum class es(“新枚举”,“强枚举”)解决了传统C ++枚举的三个问题:

  • conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer. 传统的枚举隐式转换为int,当某人不希望枚举充当整数时会导致错误。
  • conventional enums export their enumerators to the surrounding scope, causing name clashes. 传统的枚举将其枚举数导出到周围的范围,从而引起名称冲突。
  • the underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible. enum的基本类型无法指定,从而导致混乱,兼容性问题,并使前向声明变得不可能。

The new enums are "enum class" because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions). 新的枚举是“枚举类”,因为它们将传统枚举的各个方面(名称值)与类的各个方面(作用域成员和无转换)结合在一起。

So, as mentioned by other users, the "strong enums" would make the code safer. 因此,正如其他用户提到的那样,“强枚举”将使代码更安全。

The underlying type of a "classic" enum shall be an integer type large enough to fit all the values of the enum ; “经典” enum的基础类型应为整数类型,其大小应足以容纳该enum所有值; this is usually an int . 这通常是一个int Also each enumerated type shall be compatible with char or a signed/unsigned integer type. 同样,每个枚举类型都应与char或有符号/无符号整数类型兼容。

This is a wide description of what an enum underlying type must be, so each compiler will take decisions on his own about the underlying type of the classic enum and sometimes the result could be surprising. 这是对enum基础类型必须是什么的广泛描述,因此每个编译器都会自行决定经典enum的基础类型,有时结果可能令人惊讶。

For example, I've seen code like this a bunch of times: 例如,我看过很多次这样的代码:

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

In the code above, some naive coder is thinking that the compiler will store the E_MY_FAVOURITE_FRUITS values into an unsigned 8bit type... but there's no warranty about it: the compiler may choose unsigned char or int or short , any of those types are large enough to fit all the values seen in the enum . 在上面的代码中,一些幼稚的编码人员认为编译器会将E_MY_FAVOURITE_FRUITS值存储到一个无符号的8位类型中,但是并不能保证:编译器可以选择unsigned charintshort ,这些类型中的任何一个都很大足以适合enum看到的所有值。 Adding the field E_MY_FAVOURITE_FRUITS_FORCE8 is a burden and doesn't forces the compiler to make any kind of choice about the underlying type of the enum . 添加字段E_MY_FAVOURITE_FRUITS_FORCE8是一种负担,不会迫使编译器对enum的基础类型做出任何选择。

If there's some piece of code that rely on the type size and/or assumes that E_MY_FAVOURITE_FRUITS would be of some width (eg: serialization routines) this code could behave in some weird ways depending on the compiler thoughts. 如果有一些代码依赖类型大小和/或假定E_MY_FAVOURITE_FRUITS的宽度一定(例如,序列化例程),则此代码可能会根据编译器的思想以某种奇怪的方式运行。

And to make matters worse, if some workmate adds carelessly a new value to our enum : 更糟糕的是,如果某些同事不小心为我们的enum增加了新的价值:

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

The compiler doesn't complain about it! 编译器没有抱怨! It just resizes the type to fit all the values of the enum (assuming that the compiler were using the smallest type possible, which is an assumption that we cannot do). 它只是调整类型的大小以适合enum所有值(假设编译器正在使用最小的类型,这是我们不能做的假设)。 This simple and careless addition to the enum could subtlety break related code. enum这种简单粗心的添加可能会细微破坏相关代码。

Since C++11 is possible to specify the underlying type for enum and enum class (thanks rdb ) so this issue is neatly addressed: 由于C ++ 11可以为enumenum 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
};

Specifying the underlying type if a field have an expression out of the range of this type the compiler will complain instead of changing the underlying type. 如果字段的表达式不在此类型范围内,则指定基础类型,编译器将抱怨而不是更改基础类型。

I think that this is a good safety improvement. 我认为这是一个很好的安全改进。

So Why is enum class preferred over plain enum? 那么, 为什么枚举类比普通枚举更受欢迎? , if we can choose the underlying type for scoped( enum class ) and unscoped ( enum ) enums what else makes enum class a better choice?: ,如果我们可以为scoped( enum class )和unscoped( enum )枚举选择基础类型,那么还有什么使enum class成为更好的选择呢?

  • They don't convert implicitly to int . 它们不会隐式转换为int
  • They don't pollute the surrounding namespace. 它们不会污染周围的名称空间。
  • They can be forward-declared. 它们可以被预先声明。

#5楼

Enumerations are used to represent a set of integer values. 枚举用于表示一组整数值。

The class keyword after the enum specifies that the enumeration is strongly typed and its enumerators are scoped. enum后的class关键字指定枚举是强类型的,并且枚举的作用域是作用域。 This way enum classes prevents accidental misuse of constants. 这样, enum类可以防止意外滥用常量。

For Example: 例如:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

Here we can not mix Animal and Pets values. 在这里,我们不能混合动物和宠物的值。

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal

#6楼

C++11 FAQ mentions below points: C ++ 11常见问题解答提到以下几点:

conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer. 传统的枚举隐式转换为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;
}

conventional enums export their enumerators to the surrounding scope, causing name clashes. 传统的枚举将其枚举数导出到周围的范围,从而引起名称冲突。

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

The underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible. 枚举的基础类型无法指定,从而导致混乱,兼容性问题,并使前向声明变得不可能。

// 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值