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

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

но что это на самом деле означает?

5 ответов


C++ имеет два вида enum:

  1. enum classes
  2. простые enums

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

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

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

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

  • простые enums - где имена перечислителей находятся в той же области, что и перечисление и их значения неявно преобразуются в целые числа и другие типы

пример:

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


С C++11 FAQ Бьярне Страуструпа:

на enum classes ("новые перечисления"," сильные перечисления") решить три проблемы с традиционными перечислениями C++:

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

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

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

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

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

и что еще хуже, если какой-то напарник небрежно добавляет новое значение нашему 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
};

указание базового типа если поле имеет выражение вне диапазона этого типа, компилятор будет жаловаться вместо изменения базового типа.

я думаю, что это хорошее улучшение безопасности.

так почему класс enum предпочтительнее простого перечисления?, если мы можем выбрать базовый тип для уровня(enum class) и без области видимости (enum) перечисляет, что еще делает enum class лучший выбор?:

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

основное преимущество использования класса enum над обычными перечислениями заключается в том, что у вас могут быть одинаковые переменные перечисления для 2 разных перечислений и все еще могут их разрешать(что упоминалось как введите safe by 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??)

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

на 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 FAQ упоминает ниже пунктов:

обычные перечисления неявно преобразуются в 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;
}