LINUX.ORG.RU

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

 , , ,


3

3

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

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

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

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

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

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

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

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

А вот тянуть сторонний код

Вы хотите сказать, что в более-менее серьезном проекте будет только stdlib и все? Никаких сторонних библиотек, которые нужно изучать. Никаких классов/функций, написанных для конкретной предметной области конкретного проекта?

Да полноте. Даже в проекте на 50KLOC будет дофига того, что сторонний человек, не погруженный в проект, не знает и с чем обязательно придется знакомиться.

ради настолько, казалось бы, примитивной задачи, которую еще деды 50 лет назад решили и забыли (я про передачу флагов)…

Деды ее решили так, что ошибиться с передачей не того не туда как два байта…

сделать struct{bool FLAG_A, FLAG_B, … }. И потомки скажут спасибо.

Для меня показательной оказалась ситуация, в которой команда из 6 человек часа четыре в авральном режиме искала баг. Который заключался в том, что где-то в коде кто-то перепутал username и password при вызове метода db.connect потому, что оба параметра были простыми string-ами.

Нехило так, не правда ли?

Дело было в Java, но там и возможностей сделать бесплатный strong-typedef по тем временам и не было вообще, насколько я помню.

А вот в C++ можно, причем уже лет 30 как. И это реально спасает. Не только для флагов. Но даже для последовательно идущих аргументов одинакового типа в функциях/методах. Вроде функции, которая получает 4 int-а или 4 bool-а подряд.

Многие программисты о таких вещах даже не задумываются. Хотя выходов (причем простых и дешевых) можно сходу две-три штуки назвать.

Но нет, давайте поминать дедов и надеяться, что потомки скажут спасибо.

Не скажут. Проверено на себе.

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

Вы хотите сказать, что в более-менее серьезном проекте будет только stdlib и все?

У меня есть граничащее с религиозным стремление тащить в проект как можно меньше постороннего. Все обязаны ориентироваться в stdlib, но никто не обязан ориентироваться даже в Qt, boost и чем угодно сколько угодно популярном. Любая васянская либа это потенциально скорый крик джуна «кто втащил в проект это говно? Как оно вообще работает?? Дайте я перепишу!!», независимо от чоткости васяна и либы.

где-то в коде кто-то перепутал username и password

Вот здесь непонятно. Хрен с булями, допустим в моем примере не були, а строки.

db_connect({.username="user", .password="1234"});

Как именно здесь можно что-то перепутать? Это ж даже любой вебезьяне знакомо.

надеяться, что потомки скажут спасибо.

Ну хорошо. Пусть не скажут, но почему именно?

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

У меня есть граничащее с религиозным стремление тащить в проект как можно меньше постороннего.

И что, вы откажитесь затащить Asio и будете работу с socket-ами вручную делать (особенно если вам не нужна супер-пупер производительность)? Или логирование сами напишете, а не spdlog примените?

юбая васянская либа это потенциально скорый крик джуна

Так на то они и джуны, чтобы их не слушать. Пусть кричат сколько хотят.

db_connect({.username=«user», .password=«1234»});

Во-первых, например, в C++17 вы так не напишите. Будет именно что

db.connect("user", "1234");

Или

make_rect(215, 526);

Для того, чтобы преобразовать это в что-то вроде:

db.connect(username{"user"}, password{"1234"});
...
make_rect(width{215}, height{526});

уже нужно отойти от привычных для многих подходов.

Пусть не скажут, но почему именно?

Потому что наступят на грабли, оставленные дедами. Причем наступят неоднократно. Т.к. дедам привычнее было писать:

void connect(const string & user, const string & password);
...
void make_rect(int width, int height);
eao197 ★★★★★
()
Ответ на: комментарий от eao197

Мой колхозный вариант битовых флагов

#include <cstdint> 

#pragma push_macro("Self")
#undef Self
#define Self MyFlagsOption 

struct Self {
	union {
		struct {
			bool flagA: 1;
			bool flagB: 1;
			bool flagC: 1;
		};
		uint_least8_t rawData = 0;
	};
};

Self operator| (const Self& x, const Self& y) {
	return { .rawData = uint_least8_t(x.rawData | y.rawData) };
}
Self operator& (const Self& x, const Self& y) {
	return { .rawData = uint_least8_t(x.rawData & y.rawData) };
}

#pragma pop_macro("Self")

void doAction(MyFlagsOption opt) {
	
}

int main() {
	MyFlagsOption opt1 = { .flagA = 1 };
	MyFlagsOption opt2 = { .flagB = 1 };
	
	doAction(opt1 | opt2);
	doAction({ .flagA = 1 });
	doAction(MyFlagsOption({ .flagA = 1 }) | opt2);
	
	return 0;
}
bga_ ★★★★
()
Ответ на: комментарий от eao197

нужно отойти от привычных для многих подходов.

Так я ж не возражаю против отхода! Просто мой подход мне видится более щадящим, что ли. Причем и для компилятора, и для человека.

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

Мой колхозный вариант битовых флагов

Я не перешел еще на C++20, но вроде как в C++17 (и предшествующих стандартах) у вас тут UB, т.к. изменение flagA не начинает время жизни для rawData в вашем union-е Self.

О том, как оно в C++20, емнип, можно прочитать здесь: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html

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

А зачем operator&? Дань традициям, или с ним реально удобнее?

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

Просто мой подход мне видится более щадящим, что ли.

Подход выбирается исходя из того, что нужно.

Например, чтобы не путать «длину» с «высотой» может быть достаточно всего лишь:

struct width{ int v_; };
struct height { int v_; };

Чтобы не путаться в bool-ах достаточно использовать enum class.

А вот если нам нужны значения, к которым можно применять конъюнкцию и/или дизъюнкцию, то уже потребуется что-то посерьезнее.

И, если мы начинаем такие трюки применять, что какая-то шаблонная реализация strong_typedef может здорово помочь, т.к. будет содержать в себе какой-то дополнительный функционал. Например, возможность вывода в std::ostream.

В качестве примера я привел реализацию из type_safe потому, что это одна из самых продвинутых и гибких, из виденных мной в прошлом.

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

Чтобы не путаться в bool-ах достаточно использовать enum class.

О том и речь, что enum class для этого не годится совершенно. Да вот и вы сам предпочли ему strong typedef. А я и _bga - ну, назовем это «более тупой typedef», причем мой тайпдеф самый тупой.

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

Чтобы не путаться в bool-ах достаточно использовать enum class.

О том и речь, что enum class для этого не годится совершенно.

Для замены bool-ов enum class не годится?

Да ладно вам. Скажем, было:

some_handle open_handle(const std::string & name, bool auto_init, bool auto_close);
...
auto my_handle = open_handle("...", true, false);

Элементарно переделывается в:

enum class auto_init { yes, no };
enum class auto_close { yes, no };

some_handle open_handle(const std::string & name, auto_init auto_init_mode, auto_close auto_close_mode);
...
auto my_handle = open_handle("...", auto_init::yes, auto_close::no);

Так что с последовательностью bool-ов enum class просто на ура справляется.

А вот со случаем битовых флагов – не очень. Хотя, тут как посмотреть: https://wandbox.org/permlink/tpyhgltWPUIH3yVi (а в C++23 обещают to_underlying завезти).

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от 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 ★★★★★
()
Ответ на: комментарий от 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 ★★
()
Ответ на: комментарий от kvpfs

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

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

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от 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 ★★★★★
()
Ответ на: комментарий от bugfixer

Я что-то очень важное упускаю?

Да. Очень похоже, что вы судите о состоянии всей индустрии по одному очень хорошо написанному проекту.

eao197 ★★★★★
()
Ответ на: комментарий от 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 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.