ChatGPT解决这个技术问题 Extra ChatGPT

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.

But what does that really mean?

When somebody claims that some programming construct is "evil" they are trying to discourage you from thinking for yourself.
@NicolBolas: This is more of a rethorical question to provide a FAQ answer (whether this is truly Frequenty asked is a different story).
@David, there's a discussion whether this should be an FAQ or not going on which starts here. Input welcome.
@PeteBecker Sometimes they are merely trying to protect you from yourself.
geeksforgeeks.org/… This is also a good place to understand enum vs enum class.

J
Jacob Eriksson

C++ has two kinds of enum:

enum classes Plain enums

Here are a couple of examples on 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 the two?

enum classes - enumerator names are local to the enum and their values do not implicitly convert to other types (like another enum or int)

Plain enums - where enumerator names are in the same scope as the enum and their values implicitly convert to integers and other types

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 classes should be preferred because they cause fewer surprises that could potentially lead to bugs.


Good example... is there a way to combine the type safety of the class version with the namespace promotion of the enum version? That is, if I have a class A with state, and I create an enum class State { online, offline }; as a child of class A, I'd like to do state == online checks inside of A instead of state == State::online... is that possible?
Nope. The namespace promotion is a Bad Thing™ and half the justification for enum class was to eliminate it.
In C++11, you can use explicitly typed enums too, like enum Animal: unsigned int {dog, deer, cat, bird}
@Cat Plus Plus I understand that @Oleksiy says it is bad. My question was not if Oleksiy thought it was bad. My question was a request to detail what is bad about it. Specifically, why does Oleksiy, for example, consider bad about Color color = Color::red.
@Cat Plus Plus So the example's bad does not occur until the if (color == Card::red_card) line, 4 lines later than the comment (which I see now applies to the first half of the block.) 2 lines of the block gives the bad examples. The first 3 lines are not a problem. The "entire block is why plain enums are bad" threw me as I thought you meant something was wrong with those too. I see now, it is just a set-up. In any case, thanks for the feedback.
P
PaperBirdMaster

From Bjarne Stroustrup's C++11 FAQ:

The enum classes ("new enums", "strong enums") address three problems with traditional C++ enumerations: conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer. 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. 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; this is usually an int. Also each enumerated type shall be compatible with char or a signed/unsigned integer type.

This is a wide description of what an enum underlying type must be, so each compiler will take decisions on its own about the underlying type of the classic enum and sometimes the result could be surprising.

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. 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.

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 (e.g: serialization routines) this code could behave in some weird ways depending on the compiler thoughts.

And to make matters worse, if some workmate adds carelessly a new value to our 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). This simple and careless addition to the enum could subtlety break related code.

Since C++11 is possible to specify the underlying type for enum and enum class (thanks rdb) so this issue is neatly addressed:

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?:

They don't convert implicitly to int.

They don't pollute the surrounding namespace.

They can be forward-declared.


I suppose we can restrict enum base type in for regular enums as well, so long as we have C++11
Sorry, but this answer is wrong. "enum class" has nothing to do with the ability to specify the type. That's an independent feature that exists both for regular enums and for enum classes.
This is the deal: * Enum classes are a new feature in C++11. * Typed enums are a new feature in C++11. These are two separate unrelated new features in C++11. You can use both, or you can use either, or neither.
I think that Alex Allain provides the most complete simple explanation I have yet seen in this blog at [cprogramming.com/c++11/…. Traditional enum was good for using names instead of integer values and avoiding using preprocessor #defines, which was a Good Thing - it added clarity. enum class removes the concept of a numeric value of the enumerator, and introduces scope and strong typing which increases (well, can increase :-) program correctness. It moves you one step closer to thinking object oriented.
As an aside, it's always amusing when you're reviewing code and suddenly One Piece happens.
S
Saksham

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)

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.

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

@Oleksiy Ohh I didn't read your question properly. Consider is as an add-on for those who didn't know.
it's ok! I almost forgot about this
of course, you'd write enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, easily obviating namespace issues. The namespace argument is the one of the three mentioned here that I don't buy at all.
@Jo So That solution is an unnecessary workaround. Enum: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE } is comparable to Enum class: enum class Color1 { RED, GREEN, BLUE }. Accessing is similar: COLOR1_RED vs Color1::RED, but the Enum version requires you type "COLOR1" in each value, which gives more room for typos, which the namespace behaviour of an enum class avoids.
Please use constructive criticism. When I say more room for typos, I mean when you originally define the values of enum Color1, which a compiler can't catch since it would likely still be a 'valid' name. If I write RED, GREEN and so on using an enum class, than it can't resolve to enum Banana because it requires you specify Color1::RED in order to access the value (the namespace argument). There are still good times to use enum, but the namespace behavior of an enum class can often be very beneficial.
e
eigenchris

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. This way enum classes prevents accidental misuse of constants.

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

H
HolyBlackCat

It's worth noting, on top of these other answers, that C++20 solves one of the problems that enum class has: verbosity. Imagining a hypothetical enum class, Color.

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

This is verbose compared to the plain enum variation, where the names are in the global scope and therefore don't need to be prefixed with Color::.

However, in C++20 we can use using enum to introduce all of the names in an enum to the current scope, solving the problem.

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

So now, there is no reason not to use enum class.


Q
Qinsheng Zhang

do not implicitly convert to int can choose which type underlie ENUM namespace to avoid polluting happen Compared with normal class, can be declared forward, but do not have methods


S
Swapnil

C++11 FAQ mentions below points:

conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.

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

C++11 allows "non-class" enums to be typed as well. The namespace pollution issues, etc, still exist. Take a look at relevant answers that existed a long time before this one..
A
Arnaud

Because, as said in other answers, class enum are not implicitly convertible to int/bool, it also helps to avoid buggy code like:

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

To complete my previous comment, note that gcc now has a warning called -Wint-in-bool-context which will catch exactly this kind of errors.
M
Miro Kropacek

One thing that hasn't been explicitly mentioned - the scope feature gives you an option to have the same name for an enum and class method. For instance:

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