Я чув, як кілька людей рекомендували використовувати курси перерахунків на C ++ через їхню безпеку .
Але що це насправді означає?
Я чув, як кілька людей рекомендували використовувати курси перерахунків на C ++ через їхню безпеку .
Але що це насправді означає?
Відповіді:
C ++ має два види enum
:
enum class
есenum
sОсь кілька прикладів, як їх оголосити:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
Яка різниця між двома?
enum class
es - імена перерахувачів локальні для enum, і їх значення не явно перетворюються на інші типи (наприклад, інші enum
чи int
)
Звичайний enum
s - де імена перерахунку знаходяться в тій же області, що і enum, і їх значення неявно перетворюються на цілі числа та інші типи
Приклад:
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
) та uncoped ( enum
) перераховує, що ще робить enum class
кращий вибір ?:
int
.Основна перевага використання класу enum перед звичайними перерахунками полягає в тому, що у вас можуть бути однакові змінні enum для двох різних переліків і все одно можете їх вирішити (що було зазначено як безпечний тип ОП )
Наприклад:
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
як у hte нижче оператора.
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 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
.
Оскільки, як сказано в інших відповідях, enum класів не конвертована неявно в int / bool, це також допомагає уникнути помилки коду, як:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Одне, про що явно не було сказано, - функція області надає вам можливість мати те саме ім'я для методу enum та class. Наприклад:
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
};