Хотя перечисления и считаются отдельными типами данных в языке 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++ они не считаются традиционными «классами». О традиционных классах мы поговорим несколько позже.
Юра, скажи пожалуйста, если мы пишем программу, как правильно ее написать:
пишем сначала функцию main, а дальше вызываемые функции, классы, перечисления, структуры над ней дописываем или сначала готовим классы, перечисления, а потом main?
Я понимаю, что данный вопрос относится больше к теме планирования. Я все уроки начинал писать с main, пока не дошел до структур и классов)
Если есть возможность, покажи пример планирования с нуля на итоговом тесте Глава 4. Что на каком этапе делается. Или любой другой пример планирования чего-нибудь посложнее чем «Hello World»
Еще раз большое спасибо за то, что ты делаешь!!! Ты крут)
Интереса ради придумал пример с перечислениями.
может кому то будет интересно.
Класс! Я когда-то давно с++ изучал. Теперь понадобилось и когда увидел enum class у меня аж ступор был — почему оно работает как-то не так ))) Теперь всё стало понятно.
Между прочим в C# гораздо удобнее enum. Там даже можно извлекать названия полей в виде строк… А этот enum оч неудобен! И перевод его в разряд class делает его ещё более неудобным чем без… Почему вообще это придумали? И чем они вообще руководствовались? Если уж делать, то делать так чтобы было лучше, а не добавлять проблемы… Или по крайней мере так, чтобы их можно было удобно решить!!!
Использование enum class скорей вызывает массу проблем, чем удобства. Потому что да, можно обращаться через четырёхточие как элемент класса. Но только тут это удобно. Однако во всех остальных местах прут неудобства со всех щелей… Приходится писать кучу никчемных операторов просто для разных операций, коньюнкции побитовой, дизьюнкции побитовой, равенства неравенства и тд и тп… А если к несчастью вам надо извлечь данные в виде типа unsigned, то оказывается, это невозможно. Потому что надо добавить оператор преобразования типа в сам класс enum. По синтаксису это недопустимо ибо оператор приведения типа не мб внешним…. Тупик! Приходится писать явное преобразование типа… даже если вы напишете
enum EType class:unsigned{f1,f2,..} и вам нагдо привести поля к типу unsigned, то вам придётся явно написать static_cast<unsigned>(EType::f1). А представьте себе это в списке инициализации, там 100 элементов(это ещё не самый длинный!) и ваш код будет просто захламлён этими преобразованиями типа, которые по сути ничего не делают и нужны только для удовлетворения синтаксису ибо ваш enum и так этого типа … А если вы используете старый добрый enum без указания, что это класс, то ни одной из вышеуказаных проблем даже не возникнет, всё делаетес на автомате и никаких операторов не надо! Но с другой стороны, если это class, то почему туда нельзя дописать методы как в полноценный класс? Этого нет даже в последней версии С++. Возможно в какой нибудь C++25 или выше это наконец появится и enum class станет удобным. а пока что от него одни проблемы. не советую им пользоваться. к тому же вы же всегда к полям можете дописать префиксы и тем самым выделить их и у вас не возникнет конфликтов имён… ну т.е. всё ещё очень сыро…
Жёстко, раскидал по фактам действительно недо-класс получился
Прекрасная история про то как плохой класс прострелил бедному парню колено. Вот на шарпе все проще, там рефлексия и память чистить не надо.
Интересно. Первый пример запилил в vim и пробовал скомпилить — warning: comparison between ‘enum main()::Fruits’ and ‘enum main()::Colors’ [-Wenum-compare]
так это ж просто "warning" — не ошибка
Тебе надо добавить оператор сравнения… Это ещё можно сделать.Считается что они разных типов… там придётся добавить ещё много операторов если активно работать с этим enum. И это ещё не всё…
Забыли написать, что можно тип указывать:
Возвращаясь к уроку 46, можно вместо такой записи
Использовать такую:
Как плюсы, используется 4 байта для всех флагов (сюда можно затолкать и 32 флага) вместо 8 байтов + повышение читабельности кода. Единственное, что нехорошо — постоянно придётся делать явное преобразование типов:
Но если убрать ключевое слово class, то можно работать с флагами без преобразования.
Владимир, а скажи мне: в чём разница между
и
?
Если это ОЧЕпятка, то как у тебя этот код вообще работал? Если это просто теория, то набросай какую задачу можно целиком (не фрагмент) этими флагами решить. Спасиб.
Как я указал в комменте, разница в том, что emun выделит 4 байта для хранения этих флагов, в то время как каждое использование const unsigned char будет создавать переменную размером в 1 байт. Если создать 32 флага, то через const unsigned char это всё займет 32 байта, в то время, как через enum — все те же 4. Так же повышает и читабельность кода, т.к. флаги группируются в одно перечисление, к тому же получают своё имя, по которому сгруппированы. Ещё раз повторюсь, читать коммент надо было внимательнее, я там всё это указал.
Код прекрасно работает, если не знаком с битовыми масками, то тебе в 46-ой урок, читай, вникай. Там и задача как раз есть, её и реши, только вместо этого:
Используй это:
Идея, наверное, неплохая… Вот только вопрос в производительности этого кода (и читабельности тоже)… Мне что-то внутри подсказывает что в больших проектах постоянное использование static_cast будет неплохо тормозить работу проекта… Ну а без class не очень безопасно… Впрочем, если я не прав, то поправьте
Можно делать, но слово class надо убрать.
Для примера — библиотека SFML:
Color — перечисление
Red — перечислитель со значением 0xFF000000;
так что такая идея повсеместно используется.
Что-то я не вижу преимуществ enum перед enum class
Классы enum добавили позднее и это уже усовершенствованные enum. Если ваш компилятор поддерживает C++11, то смысла использовать enum в рабочих проектах, вместо классов enum — нет.