Что меняется при передаче значения enum class в функцию, принимающую целое число?
Значение enum class нельзя неявно преобразовать к целому типу, поэтому напрямую передать его в функцию, принимающую int, нельзя. Для такого преобразования требуется явное приведение, например к базовому типу перечисления.
Это защищает код от случайного смешивания логических значений перечисления с обычными числами и делает границы преобразований видимыми.
Обычные перечисления в C++ допускают неявное преобразование к целому типу, а их имена могут попадать во внешнюю область видимости. Такой подход удобен для совместимости с C, но повышает риск конфликтов имён и случайных арифметических операций над значениями, которые логически не являются числами.
enum class, появившийся в C++11, решает эту проблему за счёт типобезопасного и ограниченного перечисления: его элементы находятся внутри области перечисления, а неявное преобразование к целому запрещено.
Пусть функция принимает числовой код, а вызывающий код передаёт значение перечисления. Если преобразование неявное, компилятор пропустит вызов, даже когда функция ожидает произвольное число, а не конкретное множество состояний.
При использовании enum class такой вызов будет отвергнут. Это может потребовать явного преобразования в существующем коде, но зато ошибка становится обнаруживаемой на этапе компиляции, а не проявляется как неверная логика во время выполнения.
У enum class есть отдельный тип, не совпадающий с его базовым целочисленным типом. По умолчанию базовый тип обычно выбирается реализацией, но его можно задать явно. Даже при явно указанном базовом типе неявного преобразования в int не появляется.
Вызов с state без static_cast некорректен. Явное приведение документирует намерение программиста, однако оно не проверяет, является ли значение семантически допустимым для конкретной функции: преобразовать можно и значение, не соответствующее объявленному элементу перечисления.
Обратное преобразование из целого в enum class также требует явного приведения. Компилятор не гарантирует, что полученное число соответствует одному из перечисленных элементов, поэтому проверку диапазона или допустимости при необходимости нужно выполнять отдельно.
В подсистеме обработки состояний функция принимает код протокольной операции, а разработчик передаёт ей состояние объекта. При обычном перечислении такой вызов может скомпилироваться благодаря неявному преобразованию в число, хотя значения относятся к разным понятиям.
Возможны два решения. Использовать обычное перечисление проще для старого интерфейса, но оно сохраняет риск случайных преобразований и конфликтов имён. Оставить enum class и явно преобразовывать его в числовой код безопаснее: граница между типами становится заметной в исходном тексте.
Практически выбирают enum class, а преобразование выполняют в одном адаптере на границе с протоколом или внешним API. Внутри предметной логики сохраняется типобезопасность, а числовое представление локализуется в одном месте.
1. Вопрос: Можно ли у enum class задать базовый тип и исчезнет ли после этого запрет на неявное преобразование?
Задать базовый тип можно, например unsigned char или int, чтобы контролировать представление и размер объекта. Однако это не отменяет типобезопасность: неявное преобразование enum class к базовому типу по-прежнему запрещено. Явное приведение всё равно необходимо.
2. Вопрос: Гарантирует ли enum class, что значение всегда является одним из объявленных элементов?
Нет. Явное преобразование целого числа к типу перечисления обычно допускается, даже если число не соответствует ни одному именованному элементу. Поэтому при разборе внешних данных нужно отдельно проверять допустимый диапазон или множество значений.
3. Вопрос: В чём практическая разница между enum class и обычным enum, кроме преобразования к числу?
Элементы enum class используются с квалификатором перечисления, например State::busy, и не загрязняют окружающую область имён. Обычный enum чаще допускает использование элементов без такого квалификатора и может конфликтовать с другими именами. Поэтому enum class обычно предпочтительнее для нового кода, если не требуется специальная совместимость со старым интерфейсом.