Почему enum class считается более безопасным в использовании, чем обычный enum?
Я слышал, что некоторые люди рекомендуют использовать перечисления (enum classes) в C++ из-за их типобезопасности.
Но что это на самом деле означает?
5 ответ(ов)
C++ имеет два типа перечислений (enum
):
enum class
- Обычные
enum
Вот несколько примеров для их объявления:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // обычный enum
В чем разница между ними?
enum class
— имена перечислителей локальны для этого перечисления, и их значения не подвергаются неявному преобразованию в другие типы (например, в другойenum
илиint
).- Обычные
enum
— имена перечислителей находятся в одном пространстве имен с перечислением, и их значения неявно преобразуются в целые числа и другие типы.
Пример:
enum Color { red, green, blue }; // обычный enum
enum Card { red_card, green_card, yellow_card }; // еще один обычный enum
enum class Animal { dog, deer, cat, bird, human }; // enum class
enum class Mammal { kangaroo, deer, human }; // еще один enum class
void fun() {
// примеры плохого использования обычных enum:
Color color = Color::red;
Card card = Card::green_card;
int num = color; // без проблем
if (color == Card::red_card) // без проблем (плохо)
cout << "плохо" << endl;
if (card == Color::green) // без проблем (плохо)
cout << "плохо" << endl;
// примеры правильного использования enum class (безопасно)
Animal a = Animal::deer;
Mammal m = Mammal::deer;
int num2 = a; // ошибка
if (m == a) // ошибка (хорошо)
cout << "плохо" << endl;
if (a == Mammal::deer) // ошибка (хорошо)
cout << "плохо" << endl;
}
Заключение:
enum class
следует предпочитать, потому что они вызывают меньше неожиданных ситуаций, которые могут привести к ошибкам.
Стоит отметить, что в дополнение к другим ответам, C++20 решает одну из проблем, связанных с enum class
, а именно — многословность. Рассмотрим enum class
под названием Color
:
void foo(Color c) {
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// и так далее
}
}
Это выглядит многословно по сравнению с обычным enum
, где имена находятся в глобальной области видимости и не требуют префикса Color::
.
Однако в C++20 мы можем использовать using enum
, чтобы ввести все имена из перечисления в текущую область видимости, тем самым решая эту проблему:
void foo(Color c) {
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// и так далее
}
}
Таким образом, теперь практически нет причин избегать использования enum class
.
Основное преимущество использования enum class
по сравнению с обычными перечислениями заключается в том, что вы можете иметь одинаковые имена переменных перечислений для двух разных перечислений и при этом сможете их разрешать. Это и называется безопасностью типов (type safety), о которой упоминал автор вопроса.
Например:
enum class Color1 { red, green, blue }; // Компилируется
enum class Color2 { red, green, blue };
enum Color1 { red, green, blue }; // Не компилируется
enum Color2 { red, green, blue };
В случае обычных перечислений компилятор не сможет различить, с каким red
вы имеете дело – Color1
или Color2
, как показано в следующем примере:
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; // Ошибка компиляции (на какой red вы ссылаетесь?)
Таким образом, использование enum class
позволяет избежать подобных конфликтов и делает код более читаемым и безопасным.
Перечисления используются для представления набора целочисленных значений.
Ключевое слово class
после enum
указывает на то, что перечисление является сильно типизированным, и его перечисляемые значения имеют область видимости. Это позволяет избежать случайного использования констант.
Например:
enum class Animal { Dog, Cat, Tiger };
enum class Pets { Dog, Parrot };
В таком случае мы не можем смешивать значения Animal и Pets.
Animal a = Dog; // Ошибка: какой DOG?
Animal a = Pets::Dog; // Pets::Dog не является Animal
Таким образом, использование enum class
помогает сохранять безопасность типов и предупреждать ошибки в коде.
Вот перевод ваших пунктов на русский язык в стиле ответа на StackOverflow:
Не производите неявное преобразование в
int
. Это может привести к неожиданному поведению, особенно если вы ожидаете, что значение будет оставаться в рамках типа перечисления.Можно выбрать, какой тип будет базовым. При определении перечислений в некоторых языках программирования, таких как C#, вы можете указать, на каком типе данных будет основано перечисление (например,
byte
,int
, и т.д.).Используйте пространство имен для ENUM, чтобы избежать загрязнения. Это поможет избежать конфликта имен, особенно если вы работаете с большими проектами или библиотеками, где могут присутствовать много одинаковых имен перечислений.
В сравнении с обычным классом, ENUM может быть объявлен заранее, но не имеет методов. Это упрощает использование перечислений, так как они могут быть определены до того, как будут использованы в коде, однако, в отличие от классов, они не могут содержать поведенческих методов.
Что такое Правило трёх?
Разница между const int*, const int * const и int * const?
Почему следует использовать указатель вместо самого объекта?
Имеют ли круглые скобки после имени типа значение при использовании new?
`std::wstring` против `std::string`: когда использовать и в чем разница?