Я слышал, как некоторые люди рекомендуют использовать перечисляемые классы в C ++ из-за их безопасности типов .
Но что это на самом деле означает?
Я слышал, как некоторые люди рекомендуют использовать перечисляемые классы в C ++ из-за их безопасности типов .
Но что это на самом деле означает?
Ответы:
C ++ имеет два вида 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
)
Простые 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
Они должны быть предпочтительнее, потому что они вызывают меньше сюрпризов, которые потенциально могут привести к ошибкам.
A
с состоянием, и я создаю enum class State { online, offline };
дочерний класс A
, я хотел бы делать state == online
проверки внутри A
вместо state == State::online
... возможно ли это?
enum class
было ее устранение.
Color color = Color::red
.
if (color == Card::red_card)
строка, на 4 строки позже, чем комментарий (который я сейчас вижу, относится к первой половине блока.) 2 строки блока дают плохие примеры. Первые 3 строки не являются проблемой. «Весь блок - это то, почему простые перечисления плохи», и я подумал, что вы тоже имели в виду, что с ними что-то не так. Теперь я вижу, это просто установка. В любом случае спасибо за отзыв.
Из C ++ 11 Bjarne Stroustrup : часто задаваемые вопросы :
В
enum class
ы ( «новые перечисления», «сильные Перечисления») адрес три проблемы с перечислениями традиционными 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 предпочтительнее обычного enum? , если мы можем выбрать базовый тип для перечислений scoped ( enum class
) и unscoped ( enum
), что еще делает enum class
лучший выбор?
int
.Основное преимущество использования класса enum по сравнению с обычными перечислениями состоит в том, что вы можете иметь одинаковые переменные перечисления для 2 разных перечислений и все же можете разрешать их (что было упомянуто как безопасный тип в 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
как в приведенном ниже утверждении.
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Compile time error(which red are you refering to??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
, легко устраняя проблемы с пространством имен. Аргумент пространства имен - один из трех упомянутых здесь, которые я вообще не покупаю.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
сравним с классом Enum: enum class Color1 { RED, GREEN, BLUE }
. Доступ аналогичен: COLOR1_RED
vs Color1::RED
, но версия Enum требует, чтобы вы вводили «COLOR1» в каждом значении, что дает больше места для опечаток, которых избегает поведение пространства имен класса enum.
enum Color1
, которые компилятор не может поймать, так как это, вероятно, все еще будет «допустимым» именем. Если я пишу RED
, GREEN
и так далее , используя класс перечислимого, чем он не может решить , чтобы , enum Banana
потому что она требует от вас указать Color1::RED
, чтобы получить доступ значения (пространство имен аргументов). Есть все еще хорошие времена, чтобы использовать enum
, но поведение пространства имен enum class
может часто быть очень полезным.
Перечисления используются для представления набора целочисленных значений.
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
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;
}
В дополнение к этим другим ответам стоит отметить, что C ++ 20 решает одну из проблем enum class
: многословие. Воображая гипотетической enum class
, Color
.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
Это многословно по сравнению с простым enum
вариантом, где имена находятся в глобальной области видимости и поэтому не должны начинаться с префикса Color::
.
Тем не менее, в C ++ 20 мы можем использовать, using enum
чтобы ввести все имена в перечислении в текущую область, решая проблему.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
Так что теперь нет причин не использовать enum class
.
Поскольку, как сказано в других ответах, перечисление классов неявно не конвертируется в int / bool, это также помогает избежать ошибочного кода, такого как:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Одна вещь, которая не была явно упомянута - функция области действия дает вам возможность иметь одинаковое имя для перечисления и метода класса. Например:
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
};