Почему класс перечисления предпочтительнее простого перечисления?

Я слышал, что несколько человек рекомендуют использовать классы enum в С++ из-за безопасности типа.

Но что это значит?

Ответ 1

С++ имеет два типа enum:

  • enum class эс
  • Обычная enum

Вот несколько примеров, как объявить их:

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

В чем разница между двумя?

  • enum class es - имена перечислителя перечислены локально, и их значения неявно не преобразуются в другие типы (например, другие enum или int)

  • Plain 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 es должны быть предпочтительнее, потому что они вызывают меньше неожиданностей, которые потенциально могут привести к ошибкам.

Ответ 2

Из Bjarne Stroustrup C++ 11 Часто задаваемые вопросы:

Класс enum class es ("new enums", "strong enums") адресует три проблемы с традиционными C++ перечислениями:

  • обычные перечисления неявно преобразуются в int, вызывая ошибки, когда кто-то не хочет, чтобы перечисление выполнялось как целое число.
  • обычные перечисления экспортируют свои счетчики в окружающую область, вызывая столкновения имен.
  • базовый тип enum не может быть указан, что вызывает путаницу, проблемы совместимости и делает невозможным объявление вперед.

Новые перечисления являются "enum class", потому что они объединяют аспекты традиционных перечислений (значения имен) с аспектами классов (охваченные члены и отсутствие конверсий).

Таким образом, как упоминалось другими пользователями, "сильные перечисления" сделают код более безопасным.

Основной тип "классического" enum должен быть целым типом, достаточно большим, чтобы соответствовать всем значениям enum; это обычно int. Также каждый перечисленный тип должен быть совместим с char или целочисленным типом signed/unsigned.

Это широкое описание, что такое 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- E_MY_FAVOURITE_FRUITS типе... но там нет никаких гарантий: компилятор может выбрать unsigned char или int или short, любой из этих типов является большим достаточно, чтобы соответствовать всем значениям, указанным в enum. Добавление поля E_MY_FAVOURITE_FRUITS_FORCE8 является бременем и не заставляет компилятор делать какой-либо выбор относительно базового типа enum.

Если есть какой-то фрагмент кода, который полагается на размер шрифта и/или предполагает, что E_MY_FAVOURITE_FRUITS будет иметь некоторую ширину (например: процедуры сериализации), этот код может вести себя каким-то странным образом в зависимости от E_MY_FAVOURITE_FRUITS компилятора.

И чтобы усугубить ситуацию, если какой-то помощник неохотно добавляет новое значение нашему enum:

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

Компилятор не жалуется на это! Он просто изменяет размер типа, чтобы соответствовать всем значениям enum (предполагая, что компилятор использовал наименьший возможный тип, что является предположением, которого мы не можем сделать). Это простое и неосторожное дополнение к enum может привести к разрыву связанного кода.

Поскольку C++ 11 можно указать базовый тип для enum и enum class (thanks 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 предпочтительнее простого перечисления?, если мы можем выбрать базовый тип для enum class (enum class) и unscoped (enum) enums, что еще делает enum class лучшим выбором?

  • Они не преобразуются неявно в int.
  • Они не загрязняют окружающее пространство имен.
  • Они могут быть просрочены.

Ответ 3

Основное преимущество использования класса enum над нормальными перечислениями состоит в том, что вы можете иметь одинаковые переменные перечисления для двух разных перечислений и все еще можете их разрешить (который был указан как безопасный тип 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, как в инструкции hte ниже.

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

Ответ 4

Перечисления используются для представления набора целых значений.

Ключевое слово 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

Ответ 5

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

Ответ 6

Поскольку, как сказано в других ответах, перечисление классов неявно не конвертируется в int/bool, это также помогает избежать ошибочного кода, такого как:

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

Ответ 7

Одна вещь, которая не была явно упомянута - функция области действия дает вам возможность иметь одинаковое имя для перечисления и метода класса. Например:

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

Ответ 8

  1. неявно конвертировать в int
  2. можете выбрать, какой тип лежит в основе
  3. Пространство имен ENUM, чтобы избежать загрязнения
  4. По сравнению с обычным классом, могут быть объявлены вперед, но не имеют методов

Ответ 9

... используйте #defines, и все будет хорошо