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?


Solution 1:

C++ has two kinds of enum:

  1. enum classes
  2. 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.

Solution 2:

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

Solution 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)

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

Solution 4:

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