Почему класс enum предпочтительнее простого перечисления?
Я слышал, что несколько человек рекомендуют использовать enum классы В C++ из-за их безопасность типа.
но что это на самом деле означает?
5 ответов
C++ имеет два вида enum
:
-
enum class
es - простые
enum
s
вот несколько примеров, как их объявить:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
в чем разница между двумя?
enum class
es-перечислитель имена местные перечислению и их значениям do не неявно преобразовать в другие типы (например, другойenum
илиint
)простые
enum
s - где имена перечислителей находятся в той же области, что и перечисление и их значения неявно преобразуются в целые числа и другие типы
пример:
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 следует предпочесть, потому что они вызывают меньше сюрпризов, которые потенциально могут привести к ошибкам.
С C++11 FAQ Бьярне Страуструпа:
на
enum class
es ("новые перечисления"," сильные перечисления") решить три проблемы с традиционными перечислениями 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;
}