LINUX.ORG.RU

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

 , , ,


3

3

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

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

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

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

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

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

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

  1. И статические анализаторы не используйте. Это инструменты для студентов и неудачников.

Без рекламы не смог. Ты как сжв со своей повесточкой - во все щели.

  1. Слово const только место в коде занимает. Если вы не хотите менять переменную, то просто не будете её менять. {41}

@byko3y triggered

  1. Триграфы

Выпилили

Короче, 80% из списка встречается только у студентов, только-только начавших писать на цпп. Масштаб проблемы сильно преувеличен.

ox55ff ★★★★★
()

можно проще - писать всё под последнюю редакцию draft C++xZZZ, максимально юзая плюшки на свежесобранном компиляторе

ты-ж крут ? а-тож :-)

совет перекрывает все прочие пункты

MKuznetsov ★★★★★
()

Потыкался в челлендж - вообще непонятно как выбирать. Где именно надо выделить что бы донести до системы, что ошибка именно тут? Скажем указатель разименовали а потом проверили - что считается ошибкой, раннее разименование или поздняя проверка?

Идея конечно хорошая, но исполнение ИМНО не очень.

AntonI ★★★★
()

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

В общем, спасибо автору за труды, а команде «ПиВаС-В-Студию» — успехов в развитии и продвижении :)

Sahas ★★★★☆
()

И насчет советов:

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

Вы не поверите… но когда приходиться кодить действительно сложные выражения (всякий «матан»), то абсолютно критичным является совпадение имен переменных в коде с обозначениями в тексте который взят за основу. И если в тексте переменные зовутся a, b, c (гадских математиков проблемы программистов не волнуют) — то и в коде они будут a, b, c. Иначе это потом фиг отладишь.

  1. Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа int. Так и делайте. Не стоит нарушать традиции. {8}

@bugfixer по этому поводу высказывался неоднократно. Если я точно знаю, что у меня индекс влезает в int - я беру int.

  1. Перегрузите как можно больше операторов, в том числе и не арифметических, для как можно большего количества типов. Придавая операторам другой смысл, вы приближаетесь к созданию своего диалекта языка. Свой язык – это прикольно. А если ещё и макросы к делу подключить…

В некоторых случаях перегрузка операций делает код нагляднее и компактнее. Особенно если надо закодить численную схему занимающую пару листов А4 в терминах векторов и матриц. И макросы до введения variadic templates там здорово помогали в т.ч. А сейчас макросы и ‘<<’ здорово помогают в отладочной печати например.

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

Слово const только место в коде занимает. Если вы не хотите менять переменную, то просто не будете её менять. {41}

@byko3y triggered

Мой основной посыл был про то, что const в C++ не дает фактических гарантий константности, про замену const на constexpr, и допустимость ограниченного применения «const T&», но только при невозврате связанной константной ссылки. Но никак не просто «не используйте const». Все остальыне проявления const ничего не дают или постепенно приводит к нагромождение дублей, которые можно потерпеть разве что в сторонних либах для реализации каких-нибудь итераторов — хотя и тераторы на самом деле являются ублюдочным подобием сишного

for (char *it = p; it < end; ++it)
которое глубоко паталогично в зародыше, и потому что код под капотом прохода по какому-нибудь связанному списку совершенно неочевиден (в том числе компилятору, который не заоптимизирует нетривиальную реализацию), и потому что дает разгулье для UB. Особенно смешно на этом фоне слышать про то, что константные итераторы дают какие-то там «гарантии» — ага, если сможешь избежать чтения высвобожденной области памяти, то считай что вот тебе гаранатии обеспечены.

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)

С++ почти не использую, использую С, но есть возражения.

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

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

16. Размер указателя и int — это всегда 4 байта. Смело используйте это число. Число 4 смотрится намного изящнее, чем корявое выражение с оператором sizeof

Тут в целом верно, но некоторые скатываются к sizeof(char). Так всё же делать не следует. Хотя, теоретически, есть древние или просто сильно экзотические системы, где оно не 1, но тот код скорее всего и так и так на них сломается по куче других причин.

35. Проявите немного уважения к программистам прошлого – объявляйте все переменные в начале функций. Это традиция!

Совет хороший, сам ему следую с давних пор. Разберу возражения.

Сразу видно, какой тип имеет переменная, что облегчает понимание программы;

Имеет смысл только для функций длиной в как минимум несколько сотен строк.

Если переменная «тяжёлая» и используется только при выполнении какого-то условия, то можно улучшить производительность, создавая её только в случае необходимости. См. также V821;

Оптимизирующий компилятор без синтаксических подсказок сам разберётся когда выделять память для переменной. А неоптимизирующий - скорее всего выделит память под все локальные переменные функции сразу вне зависимости от их блочной принадлежности. Если хотите заняться ручной оптимизацией выделения памяти под переменную - то операцию с внутренней тяжёлой переменной лучше вынести в отдельную функцию. Так и понятность кода возрастёт. Или заменить стековую переменную на что-то типа malloc (кстати это может быть полезно вне зависимости от остальных соображений).

Сложнее опечататься и использовать не то имя переменной.

По-моему тут вообще ни при чём. Опечатки от этого не зависят.

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

по этому поводу высказывался неоднократно. Если я точно знаю, что у меня индекс влезает в int - я беру int.

Главный вред int-а тут в том что он знаковый. Брал бы хотя бы unsigned int, да хоть unsigned char.

На этом фоне особенно пакостно смотрится педантизм clang-разрабов, которые захардкодили в своём компиляторе неотключаемую критическую ошибку на прототип

int main(unsigned int argc, char **argv)
под предлогом того что авторы стандарта из тех же древних времён, что и индексировщики массивов int-ами, забыли туда unsigned вписать. Впрочем, это не единственная их глупость, а gcс, которым я пользуюсь, такой фигнёй не страдает.

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

Главный вред int-а тут в том что он знаковый. Брал бы хотя бы unsigned int, да хоть unsigned char.

Вредный совет. Надо максимально отказываться от беззнаковых и использовать только в спец случаях (маски, огромный char array, запланированное переполнение) ибо слишком много граблей с этим связано. Тут можно начать целый холивар, но я просто сошлюсь на таких людей как Страуструп. Самые упрямые могут растить собственные шишки.

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

но я просто сошлюсь

На других кто тоже ошибался? Знаковая арифметика создаёт ненужные ветки кода, тратит лишние байты на код и лишние такты процессора на обработку сравнений с нулём и обход отрицательных проблем. Ради чего?

в спец случаях

запланированное переполнение

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

firkax ★★★★★
()

private для параноиков. Кому они нужны, эти поля класса?

Действительно, protected хватит всем. И как потом писать неразрушающие тесты для класса?

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

тратит лишние байты на код и лишние такты процессора

Это про 2-3 инструкции при делении? Это не серьёзно даже, если это не «high performance интодробитель на MCU».

Ради чего?

Чтобы не вляпаться однажды в безобидном «a+b+c» или «a=b+c».

kvpfs ★★
()

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

За статью спасибо, Андрей, как будет время обязательно почитаю.

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

Самые упрямые могут растить собственные шишки.

Пока ни одной не выросло. Никаких реальных причин ты не привел, доказательство авторитетом доказательством не является.

Есть одна: индексация с конца, а-ля питон, чтобы a[-1] возвращало a[a.size() - 1]. Насколько это нужно – решать каждому, на мой азгляд полезность около нуля.

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

Знаковая арифметика создаёт ненужные ветки кода, тратит лишние байты на код и лишние такты процессора на обработку сравнений с нулём и обход отрицательных проблем.

Mike Pall в рассылке LuaJIT когда-то советовал использовать знаковые числа, т.к. они быстрее на современных процессорах.

tnodir
()
Ответ на: комментарий от Siborgium

Не знаю чего там смущает с колбэком, но это всё равно частность, нагенерить можно 100500 кейсов, приправить шаблонами, сложить с результатом функций из каких-то либ - и всё уже вообще неочевидно. А в основе то, что смешанная арифметика ломает ассоциативность арифметических операций, в норме этого никто не ожидает, отсюда грабли. В общем разумней не играться в кул прогера, юзать беззнаковые в исключительных случаях, да ещё и -Wsign-conversion включить.

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

Знаковая арифметика создаёт ненужные ветки кода, тратит лишние байты на код и лишние такты процессора на обработку сравнений с нулём и обход отрицательных проблем. Ради чего?

Ради того, что целые числа бывают отрицательными? Иногда это нужно. А иногда нужна закольцованность беззнаковых целых.

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

индексация с конца, а-ля питон, чтобы a[-1] возвращало a[a.size() - 1]. Насколько это нужно – решать каждому, на мой азгляд полезность около нуля.

Эммм… например при реализации всяких stencil-вычислений (решение уравнений в частных производных методом конечных разностей) одна из главных проблем это обработка границ области. И вот когда уходишь за левую границу и индекс становится отрицательным - это очень удобно. В т.ч., если граничные условия периодические, это означает индексация а-ля питон.

Конечно это можно сделать и на беззнаковых целых, но выглядеть будеть жутковато.

Еще одна полезность int - это самое быстрое целое. Иногда это важно.

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

Эммм… например при реализации всяких stencil-вычислений

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

Касательно скорости: не знал о такой особенности, но сомневаюсь, что это будет иметь значение с учетом необходимости постоянной проверки знака числа.

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

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

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

for(int i=0; i<sz; i++){ ... }
AntonI ★★★★
()
Ответ на: комментарий от kvpfs

юзать беззнаковые в исключительных случаях

Беззнаковые нужно использовать там, где отрицательные значения не имеют смысла в принципе. Например, если речь идет о размерностях или о количестве.

Насколько такие случаи будут исключительными в конкретном прикладном коде – это уже отдельный вопрос.

eao197 ★★★★★
()

Никогда не используйте enum’ы, они все равно неявно приводятся к int. Используйте int напрямую!

ЕМНИП, единственный разумный способ использования enum в старом C++ – это метафункции вида:

template<>
struct some_meta_function<0> {
  enum { value = 0 };
};

но в современном C++ надобность в этом применении исчезла за счет возможности писать:

template<>
struct some_meta_function<0> {
  static constexpr int value = 0;
};

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

И да, то что PVS-Studio может помочь в каких-то эпизодах с Сишным enum здесь вообще не в кассу. Код не должен пропускать некорректные конструкции сам по себе, без привлечения анализаторов. Сишный enum этому никак не способствует.

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

Беззнаковые нужно использовать там, где отрицательные значения не имеют смысла в принципе. Например, если речь идет о размерностях или о количестве.

Ну и что, в unsigned нельзя передать отрицательное число?

void fn(unsigned);

fn(-3);

И оно точно так же попадёт куда-то мимо.

Даже Страуструп сетовал, что мол как жаль, что крестовая СТД вся на беззнаковых типах основана (индексы), это было ошибкой.

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

А про старый enum нужно забыть как страшный сон.

Нет. enum class - вообще про другое. Никакой enum class не позволяет делать:

Struct Mode {
   using mode_t = unsigned;
   enum e_mode {
      normal = 0b1,
      edit   = 0b10
   };
};

void fn(Mode::mode_t);

fn(Mode::normal | Mode::edit);

В общем старый сишный enum на покой не собирается, это другое.

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

Ну и что, в unsigned нельзя передать отрицательное число?

А еще C++ спокойно позволяет сравнивать signed с unsigned, и даже int с float. Давайте не мешать все проблемы в одну кучу.

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

А потом оказывается, что автор тупо привык писать int a = strlen(b). И все.

Даже Страуструп сетовал, что мол как жаль, что крестовая СТД вся на беззнаковых типах основана (индексы), это было ошибкой.

А еще Страуструп не стал делать автоматический вывод типов в первых версиях C++ из-за того, что ключевое слово auto в тот момент было занято для обозначения типа памяти (в дополнение к register и static).

И что?

Все ошибаются, в том числе и Страуструп.

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

fn(Mode::normal | Mode::edit);

Делать такое на Сишных enum-ах – это закладывать мину замедленного действия.

Тут бы какой-то вариант strong typedef был бы самое оно. Ну или std::bitset.

Но уж никак не Сишный enum.

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

strong typedef был бы самое оно. Ну или std::bitset

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

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

Городить что-то трехэтажное в такой элементарщине - не вижу смысла

Либо не умеете. Либо нет в загашнике готовых реализаций.

для своего кода - годится. Наружу юзеру такое отдавать не предлагаю.

Фокус в том, что никогда не знаешь, когда код вылезет наружу и сколько он вообще проживет.

По моим наблюдениям дольше всего живет как раз самый лютый временный говнокод, который набросали «на скорую руку». А потом никто не решается это привести в порядок.

Ну и да, в качестве вишенки на тортике:

struct Mode {
   using mode_t = unsigned;
   ...
};

void fn(Mode::mode_t);

fn(-3); // Да легко!
eao197 ★★★★★
()
Ответ на: комментарий от kvpfs

Это про 2-3 инструкции при делении? Это не серьёзно даже

А по-моему, просто так плодить мусорные инструкции - это всегда плохо.

Чтобы не вляпаться однажды в безобидном «a+b+c» или «a=b+c».

Вляпаться можно как раз из-за signed-ов.

https://godbolt.org/z/P64GrGT1d, и лови потом это, а главное - ошибка очень неочевидна с виду.

Ну вот и подтверждение. Неаккуратное обращение с знаковыми числами.

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

Да, да, не делай смешанную - используй только unsigned.

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

ЯННП. Вы предлагаете убрать знаковые целые из языка вообще потому что они лично Вам ненужны? Или что?;-)

Не хотите использовать знаковые целые - не используйте. Можно подумать кто то Вам к голове пистолет приставил и заставляет писать int вместо unsigned int…

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

Ну и да, в качестве вишенки на тортике

Будто бы enum class далеко ушёл:

enum class E {
    a, b
};

void fn(E) {}

int main() {
    fn(E(30));  // Не сложнее !
}

Фокус в том, что никогда не знаешь, когда код вылезет наружу и сколько он вообще проживет.

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

kvpfs ★★
()