LINUX.ORG.RU

Развлекательная теория и практика для C++ программистов

 , , ,


3

3

Приветствую C++ программистов. Вы серьёзны и суровы. Но уверен, что развлечения и юмор вам не чужды. Поэтому сегодня у меня для вас ссылки на необычные ресурсы.

Во-первых, я написал статью наоборот. Я всегда писал, как сделать C++ код лучше. В этот раз я перешёл на тёмную сторону. Предлагаю вашему вниманию "50 вредных советов для С++ программиста". Будьте ментально аккуратны. Там зло. Если что - я вас предупреждал :).

К сожалению, некоторые программисты не согласятся, что все советы в той или иной степени вредные. Поэтому я заранее написал пояснения к наиболее неоднозначным пунктам. Думаю, у каждого найдётся коллега, кому будет полезно всё это почитать :).

Приятного чтения!

Во-вторых, мы возродили квест по быстрому нахождению ошибок и опечаток в C++ коде. Возможно, кто-то помнит, что несколько лет назад мы уж делали подобную штуку. Мы учли недостатки интерфейса и надеемся, теперь попасть в ошибку будет проще.

Удачи: Челлендж от анализатора PVS-Studio: насколько вы внимательны?

К сожалению, всё равно иногда есть неоднозначность, куда же нажать. Сделать задания идеально однозначными не получается. Прошу сильно не заморачиваться и подойти к этому именно как к развлечению, а не экзамену.

Ответ на: комментарий от eao197

Так что с последовательностью bool-ов enum class просто на ура справляется. А вот со случаем битовых флагов – не очень.

Да-да, я про наборы флагов говорил.

Хотя, тут как посмотреть: https://wandbox.org/permlink/tpyhgltWPUIH3yVi (а в C++23 обещают to_underlying завезти).

Здесь нужно немножко затоптать в себе плюсовика и пробудить обычного человека_разумного, который тут же охренеет и заорет ЗАЧЕМ ЭТО ВСЁ? Это же типичное приматывание синей изолентой.

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 1)
Ответ на: комментарий от thesis

заорет ЗАЧЕМ ЭТО ВСЁ?

Орать не нужно. Нужно спокойно учиться на чужих ошибках. Сошлюсь здесь на опыт PVS-Studio:

enum EHAlign { Left, Middle , Right  };
enum EVAlign { Top,  Center , Bottom };

void CxStatic::SetHAlign(EHAlign enuHAlign)
{

  ....
  if (enuHAlign == Center) // (1)
  ....
}

Если вы используете enum class или нормальный strong typedef, то в точке (1) получите по рукам от компилятора.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Посмотрел я на type_safe. Идея классная. Сам хотел себе сделать более безопасные int. Но кресты и так долго собираются а тут все примитивные типы обернули. Это вообще будет вечность.

А ещё если все флажки в enum class и для каждого параметра по структуре то это будет настолько дикий бойлерплейт. Проще уж на более вменяемый яп перейти или использовать стат анализатор.

bga_ ★★★★
()
Ответ на: комментарий от thesis

ЗАЧЕМ ЭТО ВСЁ?

to_underlying поддерживается во всех компиляторах уже достаточно давно( gcc с 11, clang с 13, msvc с 19.30) и это лишь однострочник, так что можно воссоздать и для более старых версий

template <class T>
constexpr std::underlying_type_t<T> to_underlying(T value) noexcept {
    return static_cast<std::underlying_type_t<T>>(value);
}
fsb4000 ★★★★★
()
Ответ на: комментарий от eao197

Это опять не про наборы флагов пример.

Если вы используете enum class

то вам надо перегружать битовые операции, обмазываясь static_cast'ами. Ну так я и обычную структуру обмазать могу, о чем говорил на прошлой странице. Мой пойнт в том, что вот был дедовский способ набивания битовых флагов, каковые флаги есть древняя и значительная часть кодинга. Потом прошли эпохи, у нас есть накопленный опыт ошибок, и человечество придумало битсет с енум классом, которые подходят для чего угодно, кроме набивания битовых флагов, хотя и могут быть использованы для этого, если у вас есть мотивация и моток синей изоленты.
Ну круто, чо.

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 1)
Ответ на: комментарий от thesis

Это опять не про наборы флагов пример.

Ну извините, я не думал, что вам не хватит фантазии представить, как это распространяется на другие случаи. Никогда не приходилось наступать на что-то вроде:

int fh = open(file_name, O_CREAT | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | O_EXCL);

или видеть подобное в чужом коде?

то вам надо перегружать битовые операции, обмазываясь static_cast’ами.

Поэтому я и говорил, что enum class с подобными задачами справляется так себе. А вот готовый шаблонный strong typedef с нужными перегрузками будет работать только в путь.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

я не думал, что вам не хватит фантазии представить, как это распространяется на другие случаи. Никогда не приходилось наступать

Я не о том, что мне нужен пример еще одной ошибки, а о примере, когда тривиальная замена unscoped enum на enum class позволяет такихт ошибок избежать. Просто в вашем (PVS'овском) примере unscoped enum заменяется идеально. С флагами - ну эээ, нет. Мелочь, а противно.

Поэтому я и говорил, что enum class с подобными задачами справляется так себе.

Ну так я тоже об этом. Просто я побродил по интернетам, а там уже навалено статеек, как при помощи enum class и простой советской перегрузки операторов... Ладно, спасибо за ответы, прекращаю брюзжание.

thesis ★★★★★
()
Ответ на: комментарий от thesis

С флагами - ну эээ, нет. Мелочь, а противно.

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

В каких-то случаях сишный enum заменяется на enum class. Но я и не утверждал, что во всех. Для тех же битовых флагов я предлагал использовать именно что strong typedef, а не enum class. Упражнения с enum class пошли уже в процессе дальнейшего обмена мнениями.

eao197 ★★★★★
()
Ответ на: комментарий от firkax

плохо использовать unsigned индексы потому, для unsigned не вполне определена операция вычитания. даже если из легального unsigned 1 вычесть вполне легальный unsigned 2, у вас будет выход за множество значений типа. то есть -1.

потому unsigned плохо использовать для индексов, если вдруг потребуется вычислить разность индексов.

также unsigned прямо просится на тип результата функции аля size(), но разность размеров - вполне обычное вычисление.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Всё наоборот. Это для signed она недоопределена (если -fstrict-overflow включить, он входит в состав -O2). А для unsigned как раз всё определено. (u32)1 - (u32)2 = (u32)4294967295. У новичков вызывает смущение тот факт, что математическое и компьютерное вычитания - это разные операции, но это просто от непривычки. На самом деле такое понятие есть и в математике, называется оно «вычитание по модулю».

Что касается разности индексов, то данная операция всегда выполняется в каком-то контексте. Причём в значительной доле случаев отрицательная разность оказывается не нужна. А там, где нужна, можно тайпкастить результат unsigned вычитания к соответствующему signed типу - эта операция гарантированно даст правильный результат, если его вообще можно представить в signed виде. А вот если нельзя - опять нужно делать вручную беззнаковой арифметикой а минус где-то запоминать.

firkax ★★★★★
()
Ответ на: комментарий от firkax

(u32)1 - (u32)2 = (u32)4294967295.

что такое по вашему - «определено»? мы получили бессмысленное по сути значение операции, зависящее от разрядности нашего конкретного типа, причем технически произошло переполнение. и если мы преобразуем его к еще более длинному беззнаковому, навроде u64 - то мы вообще потеряем всякий намек на то, как это интерпретировать.

преобразование разности двух int к более длинному int корректно, разности двух uint к более длинному uint - некорректно. некорректно потому, что обратная операция - прибавление двойки не даст 1, поскольку на длинном uint переполнения не будет.

на интах всегда будет работать обратная операция(сложение вычитаемого и результата), на беззнаковых, при преобразовании в резульата в длинный беззнаковый - нет.

alysnix ★★★
()
Ответ на: комментарий от alysnix

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

Нет, это не бессмысленное значение. Просто оторвись от понимания компьютерного вычитания как «математического с дефектами», и прими это как самостоятельную операцию. Да, она зависит от разрядности. В Си вообще много чего зависит от разрядности, это не скриптовый язык.

разности двух uint к более длинному uint - некорректно. некорректно потому, что обратная операция - прибавление двойки не даст 1, поскольку на длинном uint переполнения не будет.

Всё корректно, надо просто понимать что ты делаешь и не иметь ложных ожиданий что оно как-нить само приведётся к тому что у тебя в голове.

на интах всегда будет работать обратная операция(сложение вычитаемого и результата), на беззнаковых, при преобразовании в резульата в длинный беззнаковый - нет.

Во-первых, никаких «при». Тайпкаст - это тоже операция, она не всегда может быть переставлена местами с чем-то ещё просто так. Во-вторых, с интами тоже не будет нормально работать если в середину всунуть тайпкаст.

int32 a = (int32)(-0x7FFFFFFF) - (int32)2;
int64 b = (int64)a + 2;

итогом будет либо UB (-fstrict-overflow) либо 0x80000001 (-fno-strict-overflow) но нигде не -0x7FFFFFFF. Разница с unsigned только в том, что в нём не будет UB ни при каких флагах компилятора, а всегда будет wrap-нутое значение с другого конца диапазона.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от eao197

Смысл моего первоначального замечания был в том, что старый сишный enum в современном C++ вообще не следует использовать. В каких-то случаях сишный enum заменяется на enum class. Но я и не утверждал, что во всех. Для тех же битовых флагов я предлагал использовать именно что strong typedef, а не enum class. Упражнения с enum class пошли уже в процессе дальнейшего обмена мнениями.

Ну не надо наговаривать на enum’e сишный. Не всегда нужна вот эта вся безопасность на а-ля «strong typedef + перегрузки», не все паранойят по поводу «я инты перепутаю, я себя знаю». При желании сишный энум точно так же оборачивается в безопасную шелуху, делается это в несколько строк в отличии от той strong typedef портянки, которая должна меня от чего-то там сберечь

#include <functional>
#include <type_traits>
#include <tuple>


template <template<typename> typename Op, typename F, typename ...T>
constexpr auto make_mask(F f, T ...t) {
    if constexpr (sizeof...(t) == 0)
        return f;
    else {
        static_assert(std::is_same_v<F,
                std::tuple_element_t<0, std::tuple<T...>>>);
        return F(Op{}(f, make_mask<Op>(t...)));
    }
}

struct Mode {
    enum e : unsigned {
        one   = 0b1,
        two   = 0b10,
        three = 0b100
    };
};
void fn(Mode::e m) {}

struct Other {
    enum e {
        a, b, c
    };
};

int main() {
    static_assert(make_mask<std::bit_or>(Mode::one, Mode::two, Mode::three) == 0b111);
    static_assert(make_mask<std::bit_and>(Mode::one, Mode::two, Mode::three) == 0b000);
    static_assert(make_mask<std::bit_and>(Mode::one, Mode::one, Mode::one) == 0b1);
    fn(make_mask<std::bit_or>(Mode::one, Mode::two, Mode::three));
    //fn(Other::a);  // Error
    //fn(4);         // Error
}

make_mask() размеров в 10 строк закрывает все эти «проблемы» с безопасностью, если этого кто-то не делает, значит не особо надо, но «сишность» или какая-то придуманная ущербность энума тут точно помешать не может.

kvpfs ★★
()
Ответ на: комментарий от firkax

Всё корректно, надо просто понимать что ты делаешь и не иметь ложных ожиданий что оно как-нить само приведётся к тому что у тебя в голове.

Жесть, и называют это нормой. Понимать нужно другое - середина участка числого ряда, который покрывает целевой тип - должна быть в районе условного нуля. Если ваши сутки начанаются с 2*10^9 часов, то unsigned - в самый раз. И все эти разговоры про «строго положительному явлению нужно давать unsigned тип» - глупость, это постоянная ходьба на краю тонкого льда на костылях, всегда на грани переполнения с перспективой получить мусор. Unsigned пригоден лишь для спец задач благодаря отсутствию sign extension.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Единственное что бы я хотел - компляторы, которые умеют давать предупреждения на sign mix операции, но не жаловаться на sign cast. Чтобы хоть как-то нивелировать то недоразумение, в результате которго беззнаковый тип прочно укоренился в крестовой std, и существует море адептов этой глупости, которые тянут эту гадость в свои проекты

vector v{3};
unsigned u;
signed i;

u+i;  // warning
v[i]; // ok

С -Wsign-conversion всё же немного больно.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 2)
Ответ на: комментарий от kvpfs

не все паранойят по поводу «я инты перепутаю, я себя знаю»

Солидарен. При codebase в пару десятков MLOC вот именно «таких» залётов не было лет N-дцать как. И для меня все эти обмазывания type-safe enum’чиками выглядят тупо как code bloat. @eao197: Я что-то очень важное упускаю?

bugfixer ★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от slovazap

Конечно, всегда улыбаюсь когда жадные неудачники-проприетарищки из последних сил пытаются рекламировать свою никчёмную поделку

Полностью солидарен.

Oldboy
()
Ответ на: комментарий от kvpfs

Ну не надо наговаривать на enum’e сишный.

Простите, но это уже за гранью добра и зла. Попробуйте в режиме C++98 вот это скомпилировать:

struct Mode {
    enum e : unsigned {
        one   = 0x1,
        two   = 0x2,
        three = 0x4
    };
};
void fn(Mode::e m) {}

может лучше станет понятно Сишный у вас enum или нет. И почему у вас fn(4) не компилируется.

eao197 ★★★★★
()
Ответ на: комментарий от firkax

при замене index на unsigned проверку index<0 можно будет убрать, больше ничего не меняя - то есть она была чисто мусорным артефактом неверно выбранного типа для индекса.

а откуда взято что в плюсах индекс не может быть легально отрицательным? поставьте указатель в средину массива и адресуйте от него инджексом в разные стороны. на этом еще и выиграете в зависимости от алгоритма.

типа так:

int larr[10];
int* lptr = &larr[5];
lptr[-3] = 0;

при замене index на unsigned проверку index<0 можно будет убрать, больше ничего не меняя - то есть она была чисто мусорным артефактом неверно выбранного типа для индекса.

проверку для int за одну операцию(если индекс не может быть отрицательным) делается кастом к unsigned. разумеется обладая знаниями о представлении целых в данном случае. выбирать сразу unsigned как тип индекса, только ради проверки, вовсе необязательно.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от eao197

Попробуйте в режиме C++98 вот это скомпилировать: может лучше станет понятно Сишный у вас enum или нет. И почему у вас fn(4) не компилируется.

$ g++ 3.cpp -std=c++98
3.cpp: In function ‘int main()’:
3.cpp:11:12: error: invalid conversion from ‘int’ to ‘Mode::e’ [-fpermissive]
   11 |         fn(3);

Так же не компилируется, что я должен был понять?

То что в Си enum ведёт себя иначе - так вы сами первый назвали unscoped enum сишным в контексте разговора о плюсах, я лишь подхватил терминологию. Ваша цитата:

Так что если речь идет о C++ после C++11, то нужно либо enum class (и строго enum class, потому что enum class и старый Сишный enum – это ну очень сильно разные вещи)

Да и идея в другом - Си like enum в плюсах является безопасным, если это кому-то нужно и он принимает в функцию enum а не базовый тип, нет никакой необходимости тащить его на помойку под предлогом «для безопасности».

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Так же не компилируется, что я должен был понять?

https://wandbox.org/permlink/QQGyeoJRk2BTOZU9

prog.cc:2:14: error: scoped enums only available with '-std=c++11' or '-std=gnu++11' [-Wc++11-extensions]
   2 |     enum e : unsigned {
     |              ^~~~~~~~

Конструкция enum e : unsigned – это уже не Сишный enum.

eao197 ★★★★★
()

Depends, причём [почти] по всем пунктам.

По коротким именам переменных @firkax уже прошёлся, в т.ч. моим любимым примером: а давайте счётчики циклов вместо i,j,k длинными объявлять! Особенно если цикл один единственный внутри короткой функции. И вообще, длинные локальные переменные в короткой (не длиньше одного экрана) функции – это почти всегда маразм: они не улучшают, а наоборот ухудшают читабельность.

Ну а я в таком случае пройдусь по п.28: «И вообще, выделение памяти — зло. char c[256] хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае – на 1024.» Прямо с ходу: а давайте лучше поменяем на PATH_MAX – и ви-таки не поверите, но я не видел ни одной программы, ни одного примера кода в доках, где такое выделялось бы динамически. Всегда и везде манипуляции с именами файлов выполняются на стеке. Не удивлюсь, если это самое PATH_MAX ради стека и было придумано.

dimgel ★★★★★
()
Последнее исправление: dimgel (всего исправлений: 2)
18 июля 2022 г.