Я слышал, как несколько человек рекомендовали использовать enum классы в C ++ из-за их безопасности типов.
Но что это на самом деле означает?
Я слышал, как несколько человек рекомендовали использовать enum классы в C ++ из-за их безопасности типов.
Но что это на самом деле означает?
В C ++ есть два типа enum
:
enum class
esenum
sВот пара примеров их объявления:
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
es следует предпочесть, потому что они вызывают меньше неожиданностей, которые потенциально могут привести к ошибкам.
A
с состоянием, и я создаю enum class State { online, offline };
как дочерний элемент класса A
, я хотел бы сделать state == online
проверки внутри A
вместо _6 _... возможно ли это?
- person mark; 20.08.2013
enum class
заключалась в его устранении.
- person Puppy; 20.08.2013
enum
, что позволяет прямое объявление и более строгий контроль над компоновкой класса :)
- person Matthieu M.; 20.08.2013
Color color = Color::red
.
- person chux - Reinstate Monica; 21.08.2013
if (color == Card::red_card)
, на 4 строки позже комментария (который, как я вижу, теперь применяется к первой половине блока). 2 строки блока дают плохие примеры. Первые 3 строки не проблема. Весь блок, почему простые перечисления плохи, бросил меня, поскольку я думал, вы имели в виду, что с ними тоже что-то не так. Теперь я понимаю, это просто подстава. В любом случае спасибо за отзыв.
- person chux - Reinstate Monica; 21.08.2013
enum
типов. Но почему, ПОЧЕМУ вы не объясняете точно (как указано в вопросе) почему это плохо и это хорошо < / я>.
- person TobiMcNamobi; 12.10.2015
using
не было скорректировано в стандарте, чтобы позволить using enum class Foo;
продвигать всех членов Foo в текущую область видимости ... Разве это не было бы очень полезно, скажем, в функции токенизатора?
- person Jimmio92; 23.08.2019
Из часто задаваемых вопросов по C ++ 11 Бьярна Страуструпа:
enum class
es («новые перечисления», «строгие перечисления») решают три проблемы с традиционными перечислениями 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
неявно.enum
? Кстати, на странице ссылки на часто задаваемые вопросы вы можете прочитать это: базовый тип перечисления не может быть указан, что вызывает путаницу, проблемы совместимости и делает невозможным предварительное объявление.
- person PaperBirdMaster; 06.05.2016
enum A : unsigned char {...}
. В статье описываются версии до C ++ 11, в которых это было невозможно. Но это не имеет ничего общего с классами перечисления.
- person rdb; 06.05.2016
Основное преимущество использования класса enum по сравнению с обычными перечислениями заключается в том, что у вас могут быть одинаковые переменные перечисления для двух разных перечислений, и вы все равно можете их разрешать (что было упомянуто 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 }
, чтобы легко избежать проблем с пространством имен. Аргумент пространства имен - это один из трех упомянутых здесь аргументов, которые я вообще не покупаю.
- person Jo So; 26.10.2018
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
сравним с классом Enum: enum class Color1 { RED, GREEN, BLUE }
. Доступ аналогичен: COLOR1_RED
vs Color1::RED
, но версия Enum требует, чтобы вы вводили COLOR1 в каждое значение, что дает больше места для опечаток, которых избегает поведение пространства имен класса enum.
- person cdgraham; 05.03.2019
which gives more room for typos
. Извините, но это самый страшный аргумент, который я слышал в 2019 году. Пора немного попрактиковаться и почувствовать, что имеет значение. Я гарантирую вам, что люди не так быстро распознают идентификаторы, как компиляторы. Так что лучше не бросайте им вызов. Это убедительный аргумент.
- person Jo So; 07.03.2019
RED
, GREEN
и так далее (используя класс перечисления), не намного ли выше вероятность того, что автоматическое разрешение разрешит перечисление enum Banana
вместо перечисления Color1
? Пожалуйста, научитесь быть критичным и постарайтесь быть объективным.
- person Jo So; 07.03.2019
enum Color1
, которые компилятор не может уловить, поскольку это, скорее всего, все еще будет «допустимым» именем. Если я напишу RED
, GREEN
и так далее, используя класс enum, он не сможет разрешить enum Banana
, потому что для доступа к значению (аргументу пространства имен) требуется указать Color1::RED
. Еще бывают хорошие времена для использования enum
, но поведение enum class
в пространстве имен часто может быть очень полезным.
- person cdgraham; 07.03.2019
Перечисления используются для представления набора целочисленных значений.
Ключевое слово 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 ++ 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
.
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;
}
Поскольку, как сказано в других ответах, перечисление классов не может быть неявно преобразовано в 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
};
enum
vsenum class
. - person mr_azad   schedule 20.04.2020