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

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

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

298

Решение

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

  1. enum classэс
  2. гладкий enums

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

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

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

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

327

Другие решения

От Bjarne Stroustrup’s C ++ 11 FAQ:

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

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

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

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

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

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

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

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

Основное преимущество использования класса enum по сравнению с обычными перечислениями состоит в том, что вы можете иметь одинаковые переменные перечисления для 2 разных перечислений и все еще можете их разрешать (что упоминалось как типа сейф по ОП)

Например:

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 как в приведенном ниже заявлении.

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

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

class Ключевое слово после enum указывает, что перечисление строго типизировано и его перечислители ограничены. Сюда enum классы предотвращают случайное неправильное использование констант.

Например:

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

Здесь мы не можем смешивать значения Animal и Pets.

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

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

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

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