Хоча перерахування і вважаються окремими типами даних в мові C++, вони не такі вже й безпечні, як здаються на перший погляд, і в деяких випадках дозволять вам робити речі, які не мають сенсу. Наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> int main() { enum Fruits { LEMON, // LEMON знаходиться всередині тієї ж області видимості, що і Fruits KIWI }; enum Colors { PINK, // PINK знаходиться всередині тієї ж області видимості, що і Colors GRAY }; Fruits fruit = LEMON; // Fruits і LEMON доступні в одній області видимості (додавати префікс не потрібно) Colors color = PINK; // Colors і PINK доступні в одній області видимості (додавати префікс не потрібно) if (fruit == color) // компілятор порівнюватиме ці змінні, як цілі числа std::cout << "fruit and color are equal\n"; // і виявить, що вони рівні! else std::cout << "fruit and color are not equal\n"; return 0; } |
Коли компілятор порівнюватиме змінні fruit і color, він неявно конвертує їх в цілочисельні значення і порівняє цілі числа. Оскільки значеннями цих двох змінних є енумератори, яким присвоєно значення 0, то це означає, що у вищенаведеному прикладі fruit = color. А це не зовсім те, що повинно бути, так як fruit і color з різних перерахувань і їх взагалі не можна порівнювати (фрукт і колір!). Зі звичайними енумераторами немає способу запобігти подібним порівняння.
Для вирішення цієї проблеми в C++11 додали класи enum (або “перерахування з областю видимості”), які додають перерахуванням, як ви вже могли зрозуміти, локальну область видимості з усіма її правилами. Для створення такого класу потрібно просто додати ключове слово class відразу після enum. Наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> int main() { enum class Fruits // додання "class" до enum визначає перерахування з обмеженою областю видимості, замість стандартного "глобального" перерахування { LEMON, // LEMON знаходиться всередині тієї ж області видимості, що і Fruits Kiwi }; enum class Colors { PINK, // PINK знаходиться всередині тієї ж області видимості, що і Colors GRAY }; Fruits fruit = Fruits::LEMON; // примітка: LEMON напряму не доступний, ми повинні використовувати Fruits::LEMON Colors color = Colors::PINK; // примітка: PINK напряму не доступний, ми повинні використовувати Colors::PINK if (fruit == color) // помилка компіляції, оскільки компілятор не знає як порівнювати різні типи: Fruits і Colors std::cout << "fruit and color are equal\n"; else std::cout << "fruit and color are not equal\n"; return 0; } |
Стандартні енумератори знаходяться в тій же області видимості, що і саме перерахування (в глобальній області видимості), тому ви можете напряму отримати до них доступ (наприклад, PINK). Однак з доданням класу, який обмежує область видимості кожного енумератора до області видимості його перерахування, для доступу до енумератора потрібно буде використати оператор дозволу області видимості (наприклад, Colors::PINK). Це значно знизить ризик виникнення конфліктів імен.
Оскільки енумератори є частиною класу enum, то необхідність додавати префікси до ідентифікаторів відпадає (наприклад, можна використовувати просто PINK замість COLOR_PINK, так як Colors::COLOR_PINK вже буде зайвим).
А строгі правила типів класів enum означають, що кожен клас enum вважається унікальним типом. Це означає, що компілятор не зможе порівнювати енумератори з різних перерахувань. Якщо ви спробуєте це зробити, то компілятор видасть помилку (як у вищенаведеному прикладі).
Однак врахуйте, що ви можете порівнювати енумератори всередині одного класу enum (тому що ці енумератори є одного типу):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> int main() { enum class Colors { PINK, GRAY }; Colors color = Colors::PINK; if (color == Colors::PINK) // це нормально std::cout << "The color is pink!\n"; else if (color == Colors::GRAY) std::cout << "The color is gray!\n"; return 0; } |
З класами enum компілятор більше не зможе неявно конвертувати значення енумераторів в цілі числа. Це добре! Але іноді можуть виникати ситуації, коли потрібно буде повернути цю особливість. У таких випадках ви можете явно конвертувати енумератор класу enum в тип int, використовуючи оператор static_cast:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> int main() { enum class Colors { PINK, GRAY }; Colors color = Colors::GRAY; std::cout << color; // не працюватиме, оскільки немає неявної конвертації в тип int std::cout << static_cast<int>(color); // результатом буде 1 return 0; } |
Якщо ви використовуєте компілятор, що підтримує C++11, то немає ніякого сенсу використовувати звичайні перерахування замість класів enum.
Зверніть увагу, ключове слово class разом з ключовим словом static є одними з найбільш заплутаних в мові C++, оскільки мають різні значення в залежності від контексту. Хоча класи enum використовують ключове слово class, в C++ вони не вважаються “традиційними класами”. Про традиційні класи ми поговоримо трохи пізніше.
