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