Урок №59. Классы enum

  Юрий  | 

  |

  Обновл. 4 Сен 2021  | 

 105527

 ǀ   18 

Хотя перечисления и считаются отдельными типами данных в языке C++, они не столь безопасны, как кажутся на первый взгляд, и в некоторых случаях позволят вам делать вещи, которые не имеют смысла. Например:

Когда компилятор будет сравнивать переменные fruit и color, он неявно преобразует их в целочисленные значения и сравнит эти целые числа. Так как значениями этих двух переменных являются перечислители, которым присвоено значение 0, то это означает, что в примере, приведенном выше, fruit = color. А это не совсем то, что должно быть, так как fruit и color из разных перечислений и их вообще нельзя сравнивать (фрукт и цвет!). С обычными перечислителями нет способа предотвратить подобные сравнения.

Для решения этой проблемы в C++11 добавили классы enum (или «перечисления с областью видимости»), которые добавляют перечислениям, как вы уже могли понять, локальную область видимости со всеми её правилами. Для создания такого класса нужно просто добавить ключевое слово class сразу после enum. Например:

Стандартные перечислители находятся в той же области видимости, что и само перечисление (в глобальной области видимости), поэтому вы можете напрямую получить к ним доступ (например, PINK). Однако с добавлением класса, который ограничивает область видимости каждого перечислителя областью видимости его перечисления, для доступа к нему потребуется оператор разрешения области видимости (например, Colors::PINK). Это значительно снижает риск возникновения конфликтов имен.

Поскольку перечислители являются частью класса enum, то необходимость добавлять префиксы к идентификаторам отпадает (например, можно использовать просто PINK вместо COLOR_PINK, так как Colors::COLOR_PINK уже будет лишним).

А строгие правила типов классов enum означают, что каждый класс enum считается уникальным типом. Это означает, что компилятор не сможет сравнивать перечислители из разных перечислений. Если вы попытаетесь это сделать, компилятор выдаст ошибку (как в примере, приведенном выше).

Однако учтите, что вы можете сравнивать перечислители внутри одного класса enum (так как эти перечислители принадлежат одному типу):

С классами enum компилятор больше не сможет неявно конвертировать значения перечислителей в целые числа. Это хорошо! Но иногда могут быть ситуации, когда нужно будет вернуть эту особенность. В таких случаях вы можете явно преобразовать перечислитель класса enum в тип int, используя оператор static_cast:

Если вы используете компилятор, поддерживающий C++11, то нет никакого смысла использовать обычные перечисления вместо классов enum.

Обратите внимание, ключевое слово class вместе с ключевым словом static являются одними из самых запутанных в языке C++, поскольку результат сильно зависит от варианта применения (контекста). Хотя классы enum используют ключевое слово class, в C++ они не считаются традиционными «классами». О традиционных классах мы поговорим несколько позже.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (568 оценок, среднее: 4,90 из 5)
Загрузка...

Комментариев: 18

  1. Valdemar:

    Юра, скажи пожалуйста, если мы пишем программу, как правильно ее написать:

    пишем сначала функцию main, а дальше вызываемые функции, классы, перечисления, структуры над ней дописываем или сначала готовим классы, перечисления, а потом main?

    Я понимаю, что данный вопрос относится больше к теме планирования. Я все уроки начинал писать с main, пока не дошел до структур и классов)

    Если есть возможность, покажи пример планирования с нуля на итоговом тесте Глава 4. Что на каком этапе делается. Или любой другой пример планирования чего-нибудь посложнее чем «Hello World»

    Еще раз большое спасибо за то, что ты делаешь!!! Ты крут)

  2. fapah13:

    Интереса ради придумал пример с перечислениями.
    может кому то будет интересно.

  3. Алекс:

    Класс! Я когда-то давно с++ изучал. Теперь понадобилось и когда увидел enum class у меня аж ступор был — почему оно работает как-то не так ))) Теперь всё стало понятно.

  4. Анатолий:

    Между прочим в C# гораздо удобнее enum. Там даже можно извлекать названия полей в виде строк… А этот enum оч неудобен! И перевод его в разряд class делает его ещё более неудобным чем без… Почему вообще это придумали? И чем они вообще руководствовались? Если уж делать, то делать так чтобы было лучше, а не добавлять проблемы… Или по крайней мере так, чтобы их можно было удобно решить!!!

  5. Анатолий:

    Использование enum class скорей вызывает массу проблем, чем удобства. Потому что да, можно обращаться через четырёхточие как элемент класса. Но только тут это удобно. Однако во всех остальных местах прут неудобства со всех щелей… Приходится писать кучу никчемных операторов просто для разных операций, коньюнкции побитовой, дизьюнкции побитовой, равенства неравенства и тд и тп… А если к несчастью вам надо извлечь данные в виде типа unsigned, то оказывается, это невозможно. Потому что надо добавить оператор преобразования типа в сам класс enum. По синтаксису это недопустимо ибо оператор приведения типа не мб внешним…. Тупик! Приходится писать явное преобразование типа… даже если вы напишете
    enum EType class:unsigned{f1,f2,..} и вам нагдо привести поля к типу unsigned, то вам придётся явно написать static_cast<unsigned>(EType::f1). А представьте себе это в списке инициализации, там 100 элементов(это ещё не самый длинный!) и ваш код будет просто захламлён этими преобразованиями типа, которые по сути ничего не делают и нужны только для удовлетворения синтаксису ибо ваш enum и так этого типа … А если вы используете старый добрый enum без указания, что это класс, то ни одной из вышеуказаных проблем даже не возникнет, всё делаетес на автомате и никаких операторов не надо! Но с другой стороны, если это class, то почему туда нельзя дописать методы как в полноценный класс? Этого нет даже в последней версии С++. Возможно в какой нибудь C++25 или выше это наконец появится и enum class станет удобным. а пока что от него одни проблемы. не советую им пользоваться. к тому же вы же всегда к полям можете дописать префиксы и тем самым выделить их и у вас не возникнет конфликтов имён… ну т.е. всё ещё очень сыро…

    1. Олег:

      Жёстко, раскидал по фактам действительно недо-класс получился

    2. Андрей:

      Прекрасная история про то как плохой класс прострелил бедному парню колено. Вот на шарпе все проще, там рефлексия и память чистить не надо.

  6. Алексей:

    Интересно. Первый пример запилил в vim и пробовал скомпилить — warning: comparison between ‘enum main()::Fruits’ and ‘enum main()::Colors’ [-Wenum-compare]

    1. Илья:

      так это ж просто "warning" — не ошибка

    2. Анатолий:

      Тебе надо добавить оператор сравнения… Это ещё можно сделать.Считается что они разных типов… там придётся добавить ещё много операторов если активно работать с этим enum. И это ещё не всё…

  7. WiseWanderer:

    Забыли написать, что можно тип указывать:

  8. Владимир:

    Возвращаясь к уроку 46, можно вместо такой записи

    Использовать такую:

    Как плюсы, используется 4 байта для всех флагов (сюда можно затолкать и 32 флага) вместо 8 байтов + повышение читабельности кода. Единственное, что нехорошо — постоянно придётся делать явное преобразование типов:

    Но если убрать ключевое слово class, то можно работать с флагами без преобразования.

    1. Константин:

      Владимир, а скажи мне: в чём разница между

      и

      ?

      Если это ОЧЕпятка, то как у тебя этот код вообще работал? Если это просто теория, то набросай какую задачу можно целиком (не фрагмент) этими флагами решить. Спасиб.

      1. Владимир:

        Как я указал в комменте, разница в том, что emun выделит 4 байта для хранения этих флагов, в то время как каждое использование const unsigned char будет создавать переменную размером в 1 байт. Если создать 32 флага, то через const unsigned char это всё займет 32 байта, в то время, как через enum — все те же 4. Так же повышает и читабельность кода, т.к. флаги группируются в одно перечисление, к тому же получают своё имя, по которому сгруппированы. Ещё раз повторюсь, читать коммент надо было внимательнее, я там всё это указал.
        Код прекрасно работает, если не знаком с битовыми масками, то тебе в 46-ой урок, читай, вникай. Там и задача как раз есть, её и реши, только вместо этого:

        Используй это:

        1. Алексей:

          Идея, наверное, неплохая… Вот только вопрос в производительности этого кода (и читабельности тоже)… Мне что-то внутри подсказывает что в больших проектах постоянное использование static_cast будет неплохо тормозить работу проекта… Ну а без class не очень безопасно… Впрочем, если я не прав, то поправьте

        2. Илья:

          Можно делать, но слово class надо убрать.
          Для примера — библиотека SFML:

          Color — перечисление
          Red — перечислитель со значением 0xFF000000;
          так что такая идея повсеместно используется.

  9. Алибек:

    Что-то я не вижу преимуществ enum перед enum class

    1. Фото аватара Юрий:

      Классы enum добавили позднее и это уже усовершенствованные enum. Если ваш компилятор поддерживает C++11, то смысла использовать enum в рабочих проектах, вместо классов enum — нет.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *