Когда кто-то заявляет, что какая-то программная конструкция является «злой», он пытается отговорить вас думать за себя.
Пит Беккер
3
@NicolBolas: Это скорее риторический вопрос, чтобы дать ответ на часто задаваемые вопросы (действительно ли это часто задаваемый вопрос - другая история).
Дэвид Родригес - dribeas
@ Давид, есть обсуждение, должно ли это быть часто задаваемыми вопросами или нет, которое начинается здесь . Вход приветствуется.
ВОО
17
@PeteBecker Иногда они просто пытаются защитить вас от себя.
piccy
geeksforgeeks.org/… Это также хорошее место для понимания enumпротив enum class.
mr_azad
Ответы:
473
C ++ имеет два вида enum:
enum classэс
Обычный enumс
Вот пара примеров, как их объявить:
enumclassColor{ red, green, blue };// enum classenumAnimal{ dog, cat, bird, human };// plain enum
В чем разница между двумя?
enum classes - имена перечислителя являются локальными для перечисления, и их значения неявным образом не преобразуются в другие типы (например, другие enumили int)
Простые enums - где имена перечислителей находятся в той же области, что и перечисление, и их значения неявно преобразуются в целые и другие типы
Пример:
enumColor{ red, green, blue };// plain enum enumCard{ red_card, green_card, yellow_card };// another plain enum enumclassAnimal{ dog, deer, cat, bird, human };// enum classenumclassMammal{ kangaroo, deer, human };// another enum classvoid fun(){// examples of bad use of plain enums:Color color =Color::red;Card card =Card::green_card;int num = color;// no problemif(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;// errorif(m == a)// error (good)
cout <<"bad"<< endl;if(a ==Mammal::deer)// error (good)
cout <<"bad"<< endl;}
Вывод:
enum classОни должны быть предпочтительнее, потому что они вызывают меньше сюрпризов, которые потенциально могут привести к ошибкам.
Хороший пример ... есть ли способ объединить безопасность типов версии класса с продвижением пространства имен версии enum? То есть, если у меня есть класс Aс состоянием, и я создаю enum class State { online, offline };дочерний класс A, я хотел бы делать state == onlineпроверки внутри Aвместо state == State::online... возможно ли это?
отметка
31
Нет. Раскрутка пространства имен - это плохая вещь, и половиной оправдания enum classбыло ее устранение.
Щенок
10
В C ++ 11 вы также можете использовать явно типизированные перечисления, такие как enum Animal: unsigned int {собака, олень, кошка, птица}
Блазиус Секунд
3
@ Cat Plus Plus Я понимаю, что @ Алексей говорит, что это плохо. Мой вопрос был не в том, думал ли Алексей, что это плохо. На мой вопрос была просьба подробно рассказать, что в этом плохого. В частности, почему Алексей, например, считает плохим Color color = Color::red.
chux - Восстановить Монику
9
@Cat Plus Plus Так что пример плохого не происходит, пока if (color == Card::red_card)строка, на 4 строки позже, чем комментарий (который я сейчас вижу, относится к первой половине блока.) 2 строки блока дают плохие примеры. Первые 3 строки не являются проблемой. «Весь блок - это то, почему простые перечисления плохи», и я подумал, что вы тоже имели в виду, что с ними что-то не так. Теперь я вижу, это просто установка. В любом случае спасибо за отзыв.
В 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 предпочтительнее обычного enum? , если мы можем выбрать базовый тип для перечислений scoped ( enum class) и unscoped ( enum), что еще делает enum classлучший выбор?
Я полагаю, мы можем ограничить базовый тип enum и для обычных перечислений, при условии, что у нас есть C ++ 11
Sagar Padhye
11
Извините, но этот ответ неверен. «enum class» не имеет ничего общего с возможностью указывать тип. Это независимая функция, которая существует как для обычных перечислений, так и для перечисляемых классов.
февраля
14
Это соглашение: * Перечисление классов является новой функцией в C ++ 11. * Типизированные перечисления являются новой функцией в C ++ 11. Это две отдельные новые функции в C ++ 11. Вы можете использовать и то, и другое, или ни то, ни другое.
октября
2
Я думаю, что Алекс Аллен дает наиболее полное простое объяснение, которое я когда-либо видел в этом блоге на [ cprogramming.com/c++11/… . Традиционное перечисление было хорошо для использования имен вместо целочисленных значений и избегания использования препроцессора #defines, что было хорошо - это добавило ясности. Класс enum удаляет концепцию числового значения перечислителя и вводит объем и строгую типизацию, что увеличивает (ну, может увеличить :-) правильность программы. Это приближает вас на один шаг к мышлению, ориентированному на объект.
Джон Спенсер
2
Кроме того, это всегда забавно, когда вы просматриваете код, и вдруг происходит One Piece .
Джастин Тайм - Восстановить Монику
47
Основное преимущество использования класса enum по сравнению с обычными перечислениями состоит в том, что вы можете иметь одинаковые переменные перечисления для 2 разных перечислений и все же можете разрешать их (что было упомянуто как безопасный тип в OP)
Например:
enumclassColor1{ red, green, blue };//this will compileenumclassColor2{ red, green, blue };enumColor1{ red, green, blue };//this will not compile enumColor2{ red, green, blue };
Что касается основных перечислений, компилятор не сможет различить, redссылается ли на тип Color1или Color2как в приведенном ниже утверждении.
enumColor1{ red, green, blue };enumColor2{ red, green, blue };int x = red;//Compile time error(which red are you refering to??)
@Oleksiy Ооо, я не правильно прочитал твой вопрос. Рассмотрим это как дополнение для тех, кто не знал.
Сакшам
все нормально! Я почти забыл об этом
Алексей
конечно, вы бы написали enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, легко устраняя проблемы с пространством имен. Аргумент пространства имен - один из трех упомянутых здесь, которые я вообще не покупаю.
Джо Со
2
@Jo Так что это решение - ненужный обходной путь. Enum: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }сравним с классом Enum: enum class Color1 { RED, GREEN, BLUE }. Доступ аналогичен: COLOR1_REDvs Color1::RED, но версия Enum требует, чтобы вы вводили «COLOR1» в каждом значении, что дает больше места для опечаток, которых избегает поведение пространства имен класса enum.
cdgraham
2
Пожалуйста, используйте конструктивную критику . Когда я говорю больше места для опечаток, я имею в виду, когда вы первоначально определяете значения enum Color1, которые компилятор не может поймать, так как это, вероятно, все еще будет «допустимым» именем. Если я пишу RED, GREENи так далее , используя класс перечислимого, чем он не может решить , чтобы , enum Bananaпотому что она требует от вас указать Color1::RED, чтобы получить доступ значения (пространство имен аргументов). Есть все еще хорошие времена, чтобы использовать enum, но поведение пространства имен enum classможет часто быть очень полезным.
cdgraham
20
Перечисления используются для представления набора целочисленных значений.
classКлючевое слово после enumспецифицирует , что перечисление сильно типизированных и его переписчики являются контекстными. Таким образом, enumклассы предотвращают случайное неправильное использование констант.
обычные перечисления неявно преобразуются в int, вызывая ошибки, когда кто-то не хочет, чтобы перечисление действовало как целое число.
enum color
{Red,Green,Yellow};enumclassNewColor{Red_1,Green_1,Yellow_1};int main(){//! Implicit conversion is possibleint i =Red;//! Need enum class name followed by access specifier. Ex: NewColor::Red_1int 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'return0;}
обычные перечисления экспортируют свои перечислители в окружающую область, вызывая конфликты имен.
// Header.henum vehicle
{Car,Bus,Bike,Autorickshow};enumFourWheeler{Car,// error C2365: 'Car': redefinition; previous definition was 'enumerator'SmallBus};enumclassEditor{
vim,
eclipes,VisualStudio};enumclassCppEditor{
eclipes,// No error of redefinitionsVisualStudio,// No error of redefinitionsQtCreator};
Базовый тип перечисления не может быть указан, что вызывает путаницу, проблемы совместимости и делает невозможным предварительное объявление.
C ++ 11 позволяет также набирать «не классовые» перечисления . Проблемы загрязнения пространства имен и т. Д. Все еще существуют. Посмотрите на соответствующие ответы, которые существовали задолго до этого ..
user2864740
7
неявно конвертировать в int
можете выбрать, какой тип лежит в основе
Пространство имен ENUM, чтобы избежать загрязнения
По сравнению с обычным классом, могут быть объявлены вперед, но не имеют методов
В дополнение к этим другим ответам стоит отметить, что C ++ 20 решает одну из проблем enum class: многословие. Воображая гипотетической enum class, Color.
Это многословно по сравнению с простым enumвариантом, где имена находятся в глобальной области видимости и поэтому не должны начинаться с префикса Color::.
Тем не менее, в C ++ 20 мы можем использовать, using enumчтобы ввести все имена в перечислении в текущую область, решая проблему.
Поскольку, как сказано в других ответах, перечисление классов неявно не конвертируется в int / bool, это также помогает избежать ошибочного кода, такого как:
enumMyEnum{Value1,Value2,};...if(var ==Value1||Value2)// Should be "var == Value2" no error/warning
Чтобы завершить мой предыдущий комментарий, обратите внимание, что у gcc теперь есть предупреждение, называемое -Wint-in-bool-context, которое отлавливает именно такие ошибки.
Арно
0
Одна вещь, которая не была явно упомянута - функция области действия дает вам возможность иметь одинаковое имя для перечисления и метода класса. Например:
classTest{public:// these call ProcessCommand() internallyvoidTakeSnapshot();voidRestoreSnapshot();private:enumclassCommand// wouldn't be possible without 'class'{TakeSnapshot,RestoreSnapshot};voidProcessCommand(Command cmd);// signal the other thread or whatever};
enum
противenum class
.Ответы:
C ++ имеет два вида
enum
:enum class
эсenum
сВот пара примеров, как их объявить:
В чем разница между двумя?
enum class
es - имена перечислителя являются локальными для перечисления, и их значения неявным образом не преобразуются в другие типы (например, другиеenum
илиint
)Простые
enum
s - где имена перечислителей находятся в той же области, что и перечисление, и их значения неявно преобразуются в целые и другие типыПример:
Вывод:
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
должен быть целочисленным типом, достаточно большим, чтобы соответствовать всем значениямenum
; это обычноint
. Также каждый перечисляемый тип должен быть совместим сchar
целочисленным типом со знаком или без знака.Это широкое описание того, каким
enum
должен быть базовый тип, поэтому каждый компилятор будет самостоятельно принимать решения о базовом типе классики,enum
и иногда результат может быть неожиданным.Например, я видел такой код несколько раз:
В приведенном выше коде какой-то наивный кодер думает, что компилятор будет хранить
E_MY_FAVOURITE_FRUITS
значения в 8-битном типе без знака ... но на это нет никаких гарантий: компилятор может выбратьunsigned char
или,int
илиshort
любой из этих типов достаточно большой, чтобы вместить все значения видно вenum
. Добавление поляE_MY_FAVOURITE_FRUITS_FORCE8
является бременем и не заставляет компилятор делать какой-либо выбор в отношении базового типаenum
.Если есть некоторый фрагмент кода, который полагается на размер шрифта и / или предполагает, что он
E_MY_FAVOURITE_FRUITS
будет иметь некоторую ширину (например, подпрограммы сериализации), этот код может вести себя странным образом в зависимости от мыслей компилятора.И что еще хуже, если какой-то напарник небрежно добавляет новое значение к нашему
enum
:Компилятор не жалуется на это! Он просто изменяет размер типа, чтобы соответствовать всем значениям
enum
(при условии, что компилятор использовал наименьший возможный тип, что является предположением, которое мы не можем сделать). Это простое и небрежное дополнениеenum
может нарушить тонкость кода.Так как в C ++ 11 возможно указать базовый тип для
enum
иenum class
(спасибо rdb ), поэтому эта проблема аккуратно решена:Указывая базовый тип, если у поля есть выражение вне диапазона этого типа, компилятор будет жаловаться вместо изменения базового типа.
Я думаю, что это хорошее улучшение безопасности.
Так почему класс enum предпочтительнее обычного enum? , если мы можем выбрать базовый тип для перечислений scoped (
enum class
) и unscoped (enum
), что еще делаетenum class
лучший выбор?int
.источник
Основное преимущество использования класса enum по сравнению с обычными перечислениями состоит в том, что вы можете иметь одинаковые переменные перечисления для 2 разных перечислений и все же можете разрешать их (что было упомянуто как безопасный тип в OP)
Например:
Что касается основных перечислений, компилятор не сможет различить,
red
ссылается ли на типColor1
илиColor2
как в приведенном ниже утверждении.источник
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
, легко устраняя проблемы с пространством имен. Аргумент пространства имен - один из трех упомянутых здесь, которые я вообще не покупаю.enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
сравним с классом Enum:enum class Color1 { RED, GREEN, BLUE }
. Доступ аналогичен:COLOR1_RED
vsColor1::RED
, но версия Enum требует, чтобы вы вводили «COLOR1» в каждом значении, что дает больше места для опечаток, которых избегает поведение пространства имен класса enum.enum Color1
, которые компилятор не может поймать, так как это, вероятно, все еще будет «допустимым» именем. Если я пишуRED
,GREEN
и так далее , используя класс перечислимого, чем он не может решить , чтобы ,enum Banana
потому что она требует от вас указатьColor1::RED
, чтобы получить доступ значения (пространство имен аргументов). Есть все еще хорошие времена, чтобы использоватьenum
, но поведение пространства именenum class
может часто быть очень полезным.Перечисления используются для представления набора целочисленных значений.
class
Ключевое слово послеenum
специфицирует , что перечисление сильно типизированных и его переписчики являются контекстными. Таким образом,enum
классы предотвращают случайное неправильное использование констант.Например:
Здесь мы не можем смешивать значения Animal и Pets.
источник
C ++ 11 FAQ упоминает следующие пункты:
обычные перечисления неявно преобразуются в int, вызывая ошибки, когда кто-то не хочет, чтобы перечисление действовало как целое число.
обычные перечисления экспортируют свои перечислители в окружающую область, вызывая конфликты имен.
Базовый тип перечисления не может быть указан, что вызывает путаницу, проблемы совместимости и делает невозможным предварительное объявление.
,
,
источник
источник
В дополнение к этим другим ответам стоит отметить, что C ++ 20 решает одну из проблем
enum class
: многословие. Воображая гипотетическойenum class
,Color
.Это многословно по сравнению с простым
enum
вариантом, где имена находятся в глобальной области видимости и поэтому не должны начинаться с префиксаColor::
.Тем не менее, в C ++ 20 мы можем использовать,
using enum
чтобы ввести все имена в перечислении в текущую область, решая проблему.Так что теперь нет причин не использовать
enum class
.источник
Поскольку, как сказано в других ответах, перечисление классов неявно не конвертируется в int / bool, это также помогает избежать ошибочного кода, такого как:
источник
Одна вещь, которая не была явно упомянута - функция области действия дает вам возможность иметь одинаковое имя для перечисления и метода класса. Например:
источник