Pull to refresh

Comments 28

Древность какая-то. "В прошлом году мы говорили... почему следует избегать использования булевых параметров функций". И главным доводом против — "мы не можем узнать, что это за параметр, не заглядывая в описание функции." В то время, как любая современная IDE всё подробно показывает.

В C# давно уже сделали возможность при вместе со значением указывать имя параметра. В общем-то это задумывали для случая наличия нескольких параметров со значением по умолчанию, но и в ситуациях без них это часто удобно для улучшения читаемости:

List<int> foo = new(capacity: 42);

Даже в PHP уже сделали именованные параметры

Вообще какое-то частичное решение, ну хорошо, булевые значения заменили на что-то осмысленное, а как насчет других типов? Вот есть, к примеру, функция площади треугольника с 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 для них есть).

Из Страуструповского C++11 FAQ:

The new enums are "enum class" because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions).

Возможно, по аналогии с паттерном, когда вместо 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++ и так довольно сложноваты, вы хотите сделать их еще сложнее? Это раз, а во-вторых, это никак не защитит от яблок и апельсинов, просто не в одной и той же строке, а в двух разных. А вариант с явным определением оператора защитит.

Судя по вашим комментариям в других статьях, вы изобрели очередной язычок, который собираетесь рекламировать как серебряную пулю от всех проблем на свете? :) Не вы первый, не вы последний.

Sign up to leave a comment.