Комментарии 28
Древность какая-то. "В прошлом году мы говорили... почему следует избегать использования булевых параметров функций". И главным доводом против — "мы не можем узнать, что это за параметр, не заглядывая в описание функции." В то время, как любая современная IDE всё подробно показывает.
В C# давно уже сделали возможность при вместе со значением указывать имя параметра. В общем-то это задумывали для случая наличия нескольких параметров со значением по умолчанию, но и в ситуациях без них это часто удобно для улучшения читаемости:
List<int> foo = new(capacity: 42);
Вообще какое-то частичное решение, ну хорошо, булевые значения заменили на что-то осмысленное, а как насчет других типов? Вот есть, к примеру, функция площади треугольника с 3 параметрами - как понять чисто из вызова, это три стороны, две стороны и угол или что-то еще? Что же, делать для каждого параметра обертку над float?
делать для каждого параметра обертку над float
Иногда тоже может иметь смысл. Например, чтобы случайно (допустим, из-за обычной опечатке в коде не начать складывать или сравнивать периметр с площадью или фамилию с номером телефона. Главное, думаю, без фанатизма к этому подходить.
Смысл перечисления в его контроле, чтобы компилятор не позволял ошибиться программисту. Например, если я укажу переменную типа перечисление на Pascal/Delphi, то компилятор мне не даст в неё загрузить ничего, кроме описанного в типе перечисления. В С я могу загружать как объявленное перечисление так и просто число. Потому что в С перечисление это просто контроль уникальности константы как числа в пределах одного перечисления, а не защита от ошибки в коде. А ведь перечисления очень удобно использовать в стэйт-машинах прямо в switch/case.
Когда компилятор не защищает, есть линтер) В этом отношении я на стороне автора статьи из 2022 - enum вместо констант
Запускаешь компилирование - код собирается но не работает. Запускаешь линтер - он тебя тыкает носом на несоответствие. Ты исправляешь, запускаешь ещё раз компилирование - код собирается и работает. Это так мило, всю жизнь мечтал запускать программы.
Проверку линтером можно ведь встроить в процесс сборки, чтобы "нехороший" код вообще не собирался. Да и зачем каждый раз руками "запускать". Для этого есть автоматизированные (unit и интеграционные) тесты, которые тоже в процесс сборки и CI встраиваются.
Я понимаю, что всё это поддаётся автоматизации. Но в чём проблема контроль конкретно перечисления встроить в сам компилятор, как это сделано в других языках?
Это было бы, конечно, идеально. Но, я, вот, на C# пишу, и там тоже "защита от enum" самая минимальная - нет implicit cast. Но с explicit cast ты все равно можешь в enum засунуть любое самое бредовое значение - поэтому постоянно приходится проверять что же тебе под личиной этого enum на самом деле прилетело. Но у меня к enum-ам больше другие претензии. Если я вижу в коде enum с полутора десятками значений, то я сразу напрягаюсь, потому что уже знаю, что где-то в другом месте (причем еще и не в единственном) в коде будет какой-нибудь жуткий switch с ветками на все эти полтора десятка значений и на несколько экранов.
Ну так enum class
как раз и придуман ради того, чтобы в переменную этого типа нельзя было "загружать просто число". Но это уже C++ а не C.
Так линтер ещё в момент написания кода красненьким подчеркивает что здесь что то не то
Непонятно, почему решили добавить ключевое слово class, если оно вовсе не класс и ничего, кроме перечисления значений в него по-прежнему нельзя добавить?
Думаю, смысл был в ограничении видимости (приватности) членов этого класса.
Нет, штука конечно нужная, но почему класс-то? Если так не хотелось вводить новые ключевые слова - ну назвали бы enum namespace (тем более что using для них есть).
Возможно, по аналогии с паттерном, когда вместо enum
используют "неинстанциируемый" (например, с приватным конструктором) класс с предопределенным набором экземпляров доступных через его статические поля.
Сделали бы тогда уж возможность добавлять в enum методы - к примеру, для Color могли бы быть string name()
и uint32_t toRgba()
, а то какой-то странный класс, в котором не может быть ничего, кроме констант
Кстати, да - один из резонов по которым может иметь смысл вообще использовать вместо какого-либо enum
как раз класс с фиксированным набором инстансов. Это, например, часто используется в одном из приемов рефакторинга - "Замена условного оператора полиморфизмом".
Предполагаю, это потому что возможно изначально это задумывали как сокращение для чего-то такого:
struct EnumName
{
enum { a, b, c };
};
А потом оно обросло... гипотеза, если что, но правдоподобная.
scoped перечисления это хорошо и удобно, а вот ограничения на неявное приведение типов от enum к int никогда не понимал (от int к enum действительно должно быть только явное). В частности, это делает невозможным использование элементов перечисления как битовых масок
enum class T {
T1 = 0x1,
T2 = 0x2
};
int main()
{
T i = T::T1; // OK
int j = T::T2; // ERROR
int k = T::T1 | T::T2; // ERROR
return 0;
}
Также не понимаю запрет неявного преобразования между int и bool (сейчас во всех современных языках это ввели). С ошибками из-за таких преобразований вообще ни разу не сталкивался.
В частности, это делает невозможным использование элементов перечисления как битовых масок
Напрямую - нет, но написать оператор никто не мешает:
T operator|(T lhs, T rhs)
{
using UT = std::underlying_type_t<T>;
return static_cast<T>(static_cast<UT>(lhs) | static_cast<UT>(rhs));
}
int main()
{
T i = T::T1; // OK
T k = T::T1 | T::T2; // OK
return 0;
}
Плюс в том, что есть гарантия, что вы случайно не заOR'ите яблоки с апельсинами.
Также не понимаю запрет неявного преобразования между int и bool
Вы о каком конкретно запрете?
bool x = 100500; // OK
int y = x + true; // OK
bool z = y - y; // OK
if (x > y) { // OK
[...]
}
Запрет не в C++, а в более новых языках (в частности я столкнулся с этим в Go - пришлось писать банальную функцию конвертирования bool в int... из той же серии что и предложенный вами оператор, который по сути ничего не делает, но нужен для того чтобы код компилировался). В плюсах, когда вводили bool, это еще никому в голову не пришло:)
А чтобы не заOR'ите яблоки с апельсинами, можно запретить операции между разными enum'ами, но разрешить в некоторых случаях между enum и int.
А чтобы не заOR'ите яблоки с апельсинами, можно запретить операции между разными enum'ами, но разрешить в некоторых случаях между enum и int.
Хм, хм, enum class
как бы либо приводится к int
неявно, либо нет. В первом случае будет выполняться integral promotion, который выполняется в любом случае, ибо арифметические операции в C++ не работают с аргументами, меньшими чем int
, а во втором случае будет как сейчас. Сейчас правила "автоматических" арифметических конверсий в C++ и так довольно сложноваты, вы хотите сделать их еще сложнее? Это раз, а во-вторых, это никак не защитит от яблок и апельсинов, просто не в одной и той же строке, а в двух разных. А вариант с явным определением оператора защитит.
Как же с++ устарел и уродлив.
Эволюция enum