Помогите, у меня лапки
Я пишу статьи, посвященные написанию качественного кода и про поиск ошибок с помощью инструментов статического анализа. Однообразие наскучивает, хочется пошалить. А давайте все вместе напишем статью "100 вредных советов для С++ программиста". Я начну, а вы подхватите.


  1. Всюду используйте вложенные макросы. Так текст программы станет короче, и вы сохраните больше места на жестком диске. Заодно это развлечёт ваших коллег при отладке.
  2. Если в строковом литерале вам нужен символ табуляции, смело жмите кнопку tab. Оставьте \t для яйцеголовых. Не парься.
  3. Смело сравнивайте числа с плавающей точкой с помощью оператора ==. Раз есть такой оператор, значит им нужно пользоваться.
  4. Используйте для переменных имена из одной — двух букв. Так в одну строчку, помещающуюся на экране, можно уместить более сложное выражение.
  5. Используйте числа в программировании. Так ваша программа будет выглядеть умнее и солиднее. Согласитесь, что такие строки смотрятся хардкорно: qw = ty / 65 — 29 * s;
  6. Отключи предупреждения компилятора. Они отвлекают от работы и мешают писать компактный код.
  7. Не мешкай и не тормози. Сразу бери и используй аргументы командной строки. Например, так: char buf[100]; strcpy(buf, argv[1]);. Проверки делают только параноики, неуверенные в себе и в людях.
  8. Если что-то не работает, то, скорее всего, глючит компилятор. Попробуй поменять местами некоторые переменные и строки кода.
  9. Undefined behavior это страшилка на ночь для детей. На самом деле его не существует. Если программа работает как вы ожидали, значит она правильная. И обсуждать здесь нечего, точка.
  10. memmove — лишняя функция. Всегда и везде используйте memcpy.
  11. Помни, что размер указателя это всегда 4 байта. Используй смело это число. Число 4 смотрится намного изящнее, чем корявое выражение с оператором sizeof.
  12. Нет смысла проверять, удалось ли выделить память. На современных компьютерах её много. А если не хватило, то и незачем дальше работать. Пусть программа упадёт. Все равно уже больше ничего сделать нельзя.
  13. Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа int. Так и делай. Не стоит нарушать традиции.
  14. Используй при написании кода невидимые символы. Пусть ваш код работает магическим образом. Это прикольно.
  15. Добавляй разные вспомогательные функции и классы в пространства имён std. Ведь для тебя эти функции и классы стандартные и базовые, а раз так, им самое место в std.

Оставляйте комментарии, а позже оформлю всё это в виде единого текста. Думаю, будет прикольно. Присоединяетесь!

Комментарии (163)


  1. gecube
    03.01.2022 23:15
    +1

    Оставьте /t для яйцеголовых. Не парься.

    или \t ? :-)


    1. Andrey2008 Автор
      03.01.2022 23:17

      Запутался в символах при оформлении публикации. Fixed :)


  1. horror_x
    03.01.2022 23:16
    +10

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


    1. alliumnsk
      04.01.2022 11:53
      +4

      Еще: заменяйте точки с запятой на запятую, тогда будет быстрее работать


    1. gregorybednov
      04.01.2022 14:08

      В целом да, но при специфическом кодстайле по-моему возможны исключения.

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

      if (cond) return handler();

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

      Причем важно, что лучше всего запись именно без переноса строки и фигурных скобок одновременно. Фигурные скобки тут просто мешают читаемости, а перенос строки и написание кода "а-ля Python" может очень сильно обмануть, если вдруг придётся тело условия расширить.

      Ну а "стандартный" вариант в стиле классического С это 4 строки, в стиле Java - 3, что очень сильно разреживает код и (имхо) рассредотачивает внимание, т.к. очень серьезно отличается по плотности кода от последовательностей операторов в одном блоке кода (ср.: 1 выражение на 1 строку vs 1 выражение на 3-4 строки).

      Отдельно замечу, что циклов этот приём не касается (там лучше помириться с телом на 3-4 строчки, чем проморгать что-то не то)


      1. horror_x
        04.01.2022 17:37
        +1

        Насчёт исключений согласен. Если можно оформить более читаемо (в виде таблицы, например), то почему бы и нет.

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

        Мне приходилось одновременно вести проекты на Java/Go/C++ с разными кодстайлами, и всё-таки вариант, где после if чётко выделялся выделяется блок переносами строк, в итоге воспринимался гораздо лучше. Всё потому, что всё всегда на одних и тех же позициях относительно начала строки, нет необходимости взглядом парсить условие, чтобы найти начало блока или выражения. В итоге такой разреженный код можно осознанно скроллить гораздо быстрее, чем более плотный. Блоки в if'ах выхватываются взглядом моментально.

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


      1. rblaze
        05.01.2022 21:21

        Кто не добавлял к if (cond) goto fail; ещё одну строчку, забыв при этом поставить скобки, тот ещё добавит.

        (почему goto: https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-goto-fail-apples-ssl-bug-explained-plus-an-unofficial-patch/)


  1. aamonster
    03.01.2022 23:22
    +2

    Я один прочитал "6. ... и мешают писать компактный ад"?


  1. emaxx
    03.01.2022 23:27
    +15

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

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

    Кстати, насколько я знаю, в Rust это стандартное поведение библиотеки - немедленное завершение при OOM. То же делает аллокатор в реализации Chrome.


    1. gecube
      03.01.2022 23:33
      +8

       Но в среднестатистической программе падать на OOM - это стандартная рекомендация.

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


    1. Andrey2008 Автор
      04.01.2022 00:02

      Вредный совет. Всё не так просто. У меня была целая статья про это: Почему важно проверять, что вернула функция malloc.


      1. mayorovp
        04.01.2022 00:06
        +16

        С malloc-то всё понятно, ежели написано что иногда она таки 0 возвращает — надо бы проверить.


        Но мы же про С++ говорим, где используется new.


        1. b2soft
          04.01.2022 10:30

          Дак и new либо швыряет std::bad_alloc, либо в нешвыряющей версии nullptr отдает (безусловно, есть исключения с преопределением new и/или std::set_new_handler...)


        1. rbdr
          04.01.2022 17:18
          -2

          Если зайти достаточно глубоко в new, то можно там найти malloc.


          1. b2soft
            04.01.2022 18:28
            -1

            Но, в общем случае delete памяти выделенной через malloc, или free памяти выделенной через new - undefined behavior и рассчитывать на это не стоит.


      1. mentin
        04.01.2022 06:45
        +2

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


      1. alextretyak
        05.01.2022 00:00

        А как обстоит с этим в самой PVS-Studio? Вы проверяете удалось ли выделить память? И что делаете в случае когда не удалось?

        ИМХО, самый разумный способ разрулить это на уровне операционной системы, «замораживая» приложения в случае невозможности аллокации памяти, и предоставляя пользователю самому решать что делать: принудительно завершить приложение, либо оставив его «замороженным» закрыть другие приложения [дабы освободить немного памяти], разморозить «замороженное» приложение, сохранить свои данные и закрыть его (чтобы перезапустить).


        1. gecube
          05.01.2022 00:27
          -1

          Плохая, негодная, нерабочая стратегия. Есть же оомкиллер в линуксе. Единственная нормальная стратегия:

          1. брать столько памяти, сколько нужно и ни байтом больше. (С учётом пиковой нагрузки)

          2. Если мы крашнулись по оперативе - сохраняем данные на диск без потери и даём пользователю или демону супервизору перестартовать наше приложение

          Все остальное скорее приводит к ещё большим проблемам, чем изначально было


          1. alextretyak
            05.01.2022 01:00

            Ваша стратегия для серверов, а я имел в виду стратегию для десктопов (забыл уточнить просто).


            Если мы крашнулись по оперативе — сохраняем данные на диск без потери

            Это о каких данных идёт речь?
            И что если для сохранения на диск (в случае когда это сохранение выполняется на уровне приложения, а не операционной системы) требуется немного дополнительной памяти, а она кончилась?


        1. Andrey2008 Автор
          05.01.2022 00:31

          Каждый .cpp файл обрабатывает отдельным процессом. Поскольку какие-то пользовательские данные потеряны быть не могут, то исключение std::bad_alloc будет перехвачено и ядро спокойно завершит работу, выдав соответствующее предупреждение. Т.е. какой-то особый файл будет просто не проверен. Поскольку это крайне редкое явление, как-то специально думать про такой сценарий нет необходимости.

          А теоретически такое может быть? Да, может. Но это что-то особенное. Про один из таких интересных случаев вы можете услышать в докладе "Не связывайтесь с поддержкой C++ программистов". Это там, где про строковый литерал на 26 мегабайт.


    1. tbl
      04.01.2022 03:06
      +5

      В дополнение к демону и embedded-софту это так же относится и к библиотекам: никогда на перед не угадаешь, в каком окружении они будут использоваться. Да и валиться из библиотеки в фатальное завершение программы - моветон. Лучше вернуть ошибку, пусть тот, кто библиоткеу использует, решает, как с этим жить (или не жить) дальше.


    1. souls_arch
      04.01.2022 03:11

      В 10 и половина приложений не выдает в журнал что схлопнулось по причине нехватки озу. А можно ведь еще своп раскорячить на много гигов, да на ссд. Как думаете, сколько виндовых приложений из 100 будет его бессовестно жрать и молча причмокивать? Сама 10ка по поводу нехватки озу давно не парится. Не будет вам ни логов информативных ни bsod с перезагрузкой.


    1. mSnus
      04.01.2022 04:07
      +3

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

      Или это паттерн программирования "обидка" -- ах, операция не удалась? ну, всё!


      1. unsignedchar
        04.01.2022 09:34
        +2

        Если достаточно пол-гигабайта — зачем программмист просил гигабайт?


        1. 0xd34df00d
          04.01.2022 10:51
          +1

          Потому что с лишней памятью может быть быстрее.


          1. unsignedchar
            04.01.2022 11:00
            -1

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


            1. tyomitch
              04.01.2022 17:27

              Я недавно ковырял реализацию std::vector, которая при необходимости увеличить буфер пытается увеличить его сразу вдвое, и если не получилось — то пытается увеличить впритык для добавляемых элементов.
              А вы бы как написали? При каждом добавлении делали бы реаллокацию впритык?


              1. unsignedchar
                04.01.2022 17:31
                +1

                А вы бы как написали?

                В любой неясной ситуации нужно писать свой собственный аллокатор поверх системного ;)


        1. geher
          04.01.2022 12:35
          +1

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

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

          Еще можно просто подождать, пока память не освободится. Случаи разные бывают.


        1. mSnus
          04.01.2022 12:49
          +8

          Например, пользователь работает в графическом (текстовом, видео, аудио) редакторе, у него открыты несколько несохраненных файлов, несколько часов работы. И вот он попытался открыть большой файл... Как же он будет вас ненавидеть с походом "пусть падает"!


    1. gregorybednov
      04.01.2022 10:38
      +1

      Имеет смысл по крайней мере правильно финализировать работу программы - освободить ту память и прочие ресурсы (файлы), которые уже были заняты. В противном случае при первой же попытке работать с такой программой в автоматическом режиме (например, при помощи скрипта на bash), например вызывать её до первого успешного исполнения (то есть до экзит-кода 0), мы легко можем получить проблемы уже в пользовательской системе.


      1. mayorovp
        04.01.2022 11:43
        +4

        Занимаемую процессом память, как и прочие ресурсы (файлы), освободит ОС.


        Если что и нужно финализировать перед падением — так это сбросить буферы вывода.


      1. Kelbon
        06.01.2022 08:55
        +1

        Финализируют работу программы автоматически вызванные деструкторы. И std::terminate. В случае крайней небходимости std::terminate_handler. Ни в коем случае эта логика не должна стоять над каждым выделением памяти, особенно учитывая что оно ДОЛЖНО быть неявным в современном С++(то есть без явного new)

        В итоге для 99% программ достаточно

        int main() try {
        ...
        } catch (std::bad_alloc&){
        }


    1. firehacker
      05.01.2022 05:58
      +2

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

      А потом мы видим статьи «Почему в современном софте всё всрато»...


    1. a-tk
      06.01.2022 19:23

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


  1. emaxx
    03.01.2022 23:41
    +15

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

    Если эта рекомендация предлагает использовать беззнаковый size_t, то тоже можно поспорить... Бесчисленное число багов связано с тем, что когда от нулевого значения size_t отнимают единицу, то получают число "бесконечность". Если мы говорим о среднестатистической программе, не собирающейся ворочать гигабайты памяти, и при этом слишком "ленивы", чтобы использовать честный ptrdiff_t или итераторы, то не лучше ли старый добрый int?

    Google Style Guide тоже даёт рекомендацию в этом ключе, и даже ссылается на (правда, неназванных) членов комитета, считающих, что использование беззнаковых типов в стандартной библиотеке - это ошибка:

    Because of historical accident, the C++ standard also uses unsigned integers to represent the size of containers - many members of the standards body believe this to be a mistake, but it is effectively impossible to fix at this point. <...>

    The best advice we can provide: try to use iterators and containers rather than pointers and sizes, try not to mix signedness, and try to avoid unsigned types (except for representing bitfields or modular arithmetic).


    1. Qubi
      04.01.2022 10:32
      +1

      Наверное, тут имелись int32_t, int64_t, и так далее


    1. brumbrum
      04.01.2022 10:32

      Старый добрый int — лишний повод для UB.


  1. Skykharkov
    03.01.2022 23:44
    +12

    От себя лично добавлю. Так сказать из своего опыта.

    • Никогда не тестируйте. И не пишите тестов. Ваш код идеален, что там тестировать!

    • Всегда и везде выкатывайте любые изменения сразу на продакшен. Тестовые сервера - лишняя трата денег.

    • У всех пользователей есть десятигигабитный интернет и четырехпроцессорные ноутбуки. 21-й век на дворе все таки.

    • Любой заказчик рад оплачивать ваши счета, даже если вы ничего не делаете. Просто шлите больше счетов.

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

    • Никогда не используйте платные компоненты. Только ломанные и с как можно более "левых" иранских и пакистанских сайтов. За что этим бездельникам платить деньги, в конце концов.

    • Общайтесь с заказчиками с помощью онлайн переводчиков. У нас слова вылетели. Проблема с пониманием на той стороне.

    • Не делайте бакапы. Вы всегда сможете, за пару дней, по памяти, восстановить проект, который был в разработке больше года.

    • Чтение документации вредит вашему здоровью. Что, эти жалкие фигляры, там могут написать такого что вы не знаете.


    1. tbl
      04.01.2022 03:15
      +4

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


    1. LynXzp
      05.01.2022 02:36
      +1

      Не пользуйтесь системой контроля версий. Храните исходники прямо на сервере в виртуалке.


  1. stan_volodarsky
    04.01.2022 00:00
    +3

    С третьим пунктом не согласный я. Есть ситуации в которых сравнение вещественных чисел на равенство - осмысленное и хорошо определённое действие.


    1. emaxx
      04.01.2022 01:40
      +5

      Только надо быть осторожным, потому что даже безобидные вещи вроде "double y=f(x); ... if (y==f(x))" могут работать неправильно (из-за промежуточных вычислений в 80-битных регистрах, насколько я понимаю).

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


      1. stan_volodarsky
        06.01.2022 00:02

        Тот пример, что вы привели - это вариант обмена корректности программы на скорость исполнения. Выигрыш обычно не велик, баги чудовищны.

        Я привык думать что "детерминированные вычисления детерминированы". Нарушение этого правила - жуткая головная боль.

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

        Ах, да, вывод: приведите компилятор в соответствие с IEEE-754, имейте представление когда вычисления с плавающей запятой точные, когда нет (из-за округления) и будет вам счастье.

        Ещё надо с утра повторять десять раз: "Эпсилон - часть входных данных. Я никогда не буду вписывать фиксированный эпсилон в код моей программы.".


        1. masai
          06.01.2022 00:19
          +1

          приведите компилятор в соответствие с IEEE-754

          Этого мало. При вычислениях с плавающей точкой важен, например, порядок вычислений. Это ещё и оптимизации надо отключать.

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

          Но всё это, конечно, зависит от задачи. Универсального совета нет.


          1. stan_volodarsky
            06.01.2022 09:51
            +1

            Никогда не видел чтобы оптимизатор переупорядочивал вещественные операции. Именно по причине неассоциативности операций из-за округления. Покажите пример.


            1. masai
              06.01.2022 15:17
              +1

              Я имел в виду `-funsafe-math-optimizations` (его включает `-ffast-math`). Но вообще, я согласен, мой комментарий не совсем корректный, так как этот параметр по умолчанию выключен (собственно, это требование IEEE754-2008, чтобы по умолчанию не было меняющих значение оптимизаций).


            1. emaxx
              06.01.2022 16:44
              +2

              Компилятор от Intel, например, по умолчанию разрешает себе делать такого рода преобразования при оптимизации. Это контролируется параметром fp-model, но по умолчанию его значение - "fast=1", которое явно документировано как "Value Safety: Unsafe", "Floating-Point Expression Evaluation: Unknown", "Floating-Point Contractions: Yes".

              См. https://www.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/floating-point-operations/understanding-floating-point-operations/floating-point-optimizations.html и, например, конкретные примеры в https://indico.cern.ch/event/166141/sessions/125686/attachments/201416/282784/Corden_FP_control.pdf.

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


    1. Woodroof
      05.01.2022 23:24
      +3

      И, к сожалению, встречал, что используют "нельзя сравнивать вещественные" как мантру, не разбираясь. И эта статья этому только способствует :)

      Если числа не являются результатом вычислений, то сравнивать можно и нужно.


  1. ncr
    04.01.2022 00:22
    +11

    • Не пользуйтесь стандартной библиотекой языка. Что может быть интереснее, чем написать свои строки и списки с уникальным синтаксисом и семантикой?
    • Смартпоинтеры и прочее RAII от лукавого, всеми ресурсами надо управлять вручную, это делает код простым и понятным.
    • И вообще, выделение памяти — зло. char c[256] хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае на 1024.
    • Глобальные переменные очень удобны, т.к. к ним можно обращаться отовсюду.

    Так неинтересно, список можно продолжать бесконечно.


    1. masai
      04.01.2022 03:29
      +2

      Что может быть интереснее, чем написать свои строки

      Тем более, даже Яндекс так делает. :)

      управлять вручную, это делает код простым и понятным

      Выделяешь и всё. Когда процесс завершится, система сама освободит всё.


      1. unsignedchar
        04.01.2022 09:41
        +1

        Выделяешь и всё. Когда процесс завершится, система сама освободит всё.


        В копилку вредных советов.
        Потом этот код либо кто-то вызовет в цикле, либо освободит память 2 раза ;)


        1. masai
          04.01.2022 15:15

          В копилку вредных советов.

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


          1. unsignedchar
            04.01.2022 15:52

            Когда вся программа состоит из 1 функции main() и предназначена для олимпиады — why not.


            1. masai
              04.01.2022 21:44

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


  1. datacompboy
    04.01.2022 00:27
    +4

    я бы дополнил 4е правило: используйте простую систему именования переменных! зачем выдумывать разные? просто берём: int o, O, oo, oO, o0, Oo, OO, O0.


    1. wadeg
      04.01.2022 02:54
      +11

      Так шутили еще наши деды
      Начотдела приходит к своим программисткам:
      — Что у вас за код, смотреть противно. Даже идентификаторы: a, aa, aaa, a1, a2, a3… Так сейчас никто не пишет, все используют длинные мнемонически имена. Ясно?
      — Ясно!
      Заглядывает через неделю. Весь код переписан, везде переменные ДлинноеМнемоническоеИмя1, ДлинноеМнемоническоеИмя2, ДлинноеМнемоническоеИмя3…


      1. LynXzp
        05.01.2022 02:39

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


        1. DimanDimanyich
          05.01.2022 20:02

          если бы не ограничения на наименование переменных , то было бы ваще супер!


          1. unsignedchar
            05.01.2022 20:13

            Есть perl с его прекрасными переменными $- $+ $* $.… Жаль что в C++ так нельзя.


  1. Racheengel
    04.01.2022 00:29
    +11

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


    1. Mike_666
      04.01.2022 03:07
      +3

      Не забывайте приправлять их макросами.


      1. unsignedchar
        04.01.2022 09:48
        +4

        Но макросы должны конфликтовать с чем-то. Иначе не интересно будет. Хорошая идея — позаимствовать систему именований из ядра Linux — там много красивых имён. Кто будет портировать код с такими именами — обхохочется ;).


    1. BraveBanana
      04.01.2022 10:33

      Вероятно, нужно просто правил но работать с шаблонами;)


  1. Shaz
    04.01.2022 00:59

    А давайте все вместе напишем статью "100 вредных советов для С++ программиста". Я начну, а вы подхватите.

    Лучше потом книгу выпустите. С примерами.


  1. Goerging
    04.01.2022 01:07
    +2

    Часть из них я бы назвал "советами для олимпиадников"


  1. SShtole
    04.01.2022 01:32
    +9

    Некоторые советы очень даже ничего.

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

    Например, так:

    ::SetWindowText(hWnd, _T("Text"));


    Вложенный макрос.

    Если в строковом литерале вам нужен символ табуляции, смело жмите кнопку tab. Оставьте \t для яйцеголовых. Не парься.

    Я бы вообще два раза подумал, прежде, чем хардкодить табуляцию в литерале. Хотя бы потому, что слеш легко перепутать с бэкслешем. Это про «технические» литералы, ресурсные лучше выносить в ресурсы.

    Кстати, всегда включаю Show visual spaces.

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

    Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни».


    1. Sixshaman
      04.01.2022 18:07

      Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни

      А в какой статье? Интересно почитать, т.к. сам стараюсь вместо i/j/k использовать itemIndex.


      1. SShtole
        04.01.2022 18:26

        С ходу не могу найти, к сожалению. Цитировал по памяти. Я смутно вспоминаю, что там речь шла про собеседования, но это не The Guerrilla Guide. Если позже найду, обязательно отпишусь.


      1. emaxx
        04.01.2022 18:48

        Google Style Guide, например, содержит явную рекомендацию - см. сниппет с кодом-"антипаттерном":

        for (int foo_index = 0; foo_index < foos.size(); ++foo_index) {  // Use idiomatic i

        https://google.github.io/styleguide/cppguide.html#General_Naming_Rules


      1. masai
        04.01.2022 21:50
        +3

        Если именно itemIndex, то это ничем не лучше i. Использовать i/j/k для индексов — это общепринятая практика, то есть, когда видишь i, то и так понимаешь, что это item index. Длинное название новой информации не даёт, а читать труднее.


        1. Sixshaman
          04.01.2022 21:58
          -1

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


  1. F0iL
    04.01.2022 01:39

    Вот тут есть ещё.


  1. Amomum
    04.01.2022 01:41
    +10

    • Выравнивание и единый стиль - для слабаков, которые не хотят вчитываться в ваш код. Заставьте их!

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

    • Злые языки говорят, что goto считается вредным, но это чушь. Этот оператор очень мощен и способен заменить почти все другие конструкции языка, старайтесь пользоваться им почаще. А для переходов между функциями используйте setjmp/longjmp

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

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

    • Используйте как можно больше разных систем сборки и пакетных менеджеров, покажите, что вы в курсе современных тенденций! Разумеется, версии кода в пакетах для разных менеджеров должны быть немного разными, иначе пользователи заскучают.

    • Вы ведь слышали, что стандартные примитивные типы в С и С++ не имеют гарантированной длины в битах? Чтобы справится с этой проблемой лучше всего завести свои псевдонимы для примитивных типов, с указанной фиксированной длиной. Ни в коем случае не пользуйтесь типами из stdint.h, ведь существование этого файла не гарантируется стандартом, а значит, ваш код не будет действительно переносимым!

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

    • Использовать венгерскую нотацию - тоже хорошая, проверенная временем практика; посмотрите в конце концов на WinAPI


    1. KoCMoHaBT61
      04.01.2022 03:02
      -6

      Всегда и везде используйте enum'ы. #define для слабаков. Постарайтесь как можно чаще использовать enum'ы с разрывами. Также, для строгой типизации не используйте bool, а используйте enum MyFlagType { Yes, No } или enum MySecondFlagType { Maybe, No, Yes } Так приятно, когда человек, отсчитывая по строчке от начала разрыва узнаёт, что загадочное число 27543 в логе означает MSG_CLOSEALL.


      1. Amomum
        04.01.2022 03:04
        +8

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


      1. SShtole
        04.01.2022 06:43
        +19

        Я, честно говоря, немного запутался. Это всё ещё вредные советы или уже пошли полезные?

        Во-первых, enum'ы, при прочих равных, считаются лучше, чем define'ы. Чем лучше? Чем define'ы Ну, например, тем, что их можно определять на том же уровне, где они используются. Что делает код намного удобочитаемее. (Читаешь неймспейс — сразу видишь все классы и относящиеся к ним enum'ы).

        Во-вторых, bool, вообще-то, действительно часто советуют заменять на enum'ы. Чтобы понять, почему, предлагается взглянуть на следующий псевдокод:

        CreateWindow("MyClass",
            "MyTitle",
            true,
            false,
            false,
            true,
            true);


        …и сравнить с таким вариантом:

        CreateWindow("MyClass",
            "MyTitle",
            WindowVisibility::Visible,
            WindowState::Disabled,
            WindowChild::No,
            WindowType::ToolWindow,
            WindowModal::Modal);


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


    1. masai
      04.01.2022 03:42
      +1

      • А ещё можно switch вместо goto использовать как в Duff's device — ставим метки case на разные части тела одного цикла.


    1. lgorSL
      04.01.2022 05:18

      Если goto использовать нельзя (например, из-за линтера или код-стайла), то можно попробовать цикл do ... while(true/false) в сочетании сbreak/continue.


      1. unsignedchar
        04.01.2022 09:55

        А что не так с бесконечным циклом? КМК, использовать его вместо бесконечного цикла вполне нормально.


        1. mayorovp
          04.01.2022 11:48

          Бесконечный цикл с while(true) и break — это нормально, называется "цикл с условием посередине".


          Бесконечный цикл с while(false) — это WTF, который допустим лишь в макросах.


          1. gecube
            04.01.2022 11:53

            Бесконечный цикл с while(true) и break — это нормально, называется "цикл с условием посередине".

            если это условие выполняется в принципе... хотя по-всякому бывает :-) мало ли это какой-то софт для контроллеров.


          1. lgorSL
            04.01.2022 13:57
            +2

            do {
              ...
              if (smth) break;
              ...
            } while(false)

            Выглядит как цикл, но по-сути это goto в конец блока. Чтобы понять это, надо дочитать до конца цикла. Особенно весело, когда такие "циклы" идут вперемежку с нормальными.


            1. unsignedchar
              04.01.2022 14:05
              +1

              Это обычный if, только зачем-то запутанный.

              if (! smth) {
                ...}


              1. SShtole
                04.01.2022 15:25
                +2

                Предполагается, что их несколько.


                1. unsignedchar
                  04.01.2022 15:50
                  +1

                  Возможно, это кусок старинного кода, написанного с применением goto. Вот так вот переписанный. Или авторское know-how, чтобы никто не понял, что там происходит.


                  1. IntActment
                    05.01.2022 04:24

                    Это, скорее, вариант быстрого return, чья область действия ограничена одним блоком do {} while вместо всей функции. Причин использования может быть несколько: может быть невыгодно выделять блок с do {} while в отдельную функцию (например, придется передавать слишком много локальных переменных в эту самую функцию), избавление от дублирования кода — иными словами, существенно сокращает количество кода с повышением удобочитаемости.


                    1. unsignedchar
                      05.01.2022 08:46
                      +1

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


  1. Izaron
    04.01.2022 01:54
    +6

    • Пиши в хидерах код а-ля using namespace std; - коллеги скажут спасибо, за то что им не придется это писать в каждом .cpp!

    • Подключай как можно больше хидеров, чтобы каждый .cpp файл раскрывался в 1mln строк - коллеги скажут спасибо за то, что у них больше времени на перекур во время пересборки!

    • Функция должна вернуть больше чем одно значение? Есть крутой лайфхак - записывай результаты в указатель! Для трёх значений это int process(int arg, int* res2, int* res3).


    1. gecube
      04.01.2022 09:36
      -4

      int process(int arg, int* res2, int* res3)

      может все-таки:

      int process(int arg, int& res2, int& res3)

      ? по сути то же самое, но немножко более с++ нативно


      1. fse
        04.01.2022 18:17
        -4

        Подозреваю, что коллеги предлагают написать класс TheProcessFunctionResults... Ну, и, конечно, определить для класса конструкторы, сеттеры, геттеры и, возможно, операторы. Ну а метод TheProcessFunctionResults::stringifyError(), как вишенка на торте, не оставит никаких сомнения у читателя в необходимости наличия этого класса.

        Извиняюсь. Сарказм от сишника.


        1. mayorovp
          04.01.2022 18:44
          +1

          А что сарказм-то? Класс и правда нужен. Правда, можно обойтись std::tuple<int, int, int>, но отдельный класс с именованными полями ещё лучше.


          Аксцессоры только этому классу не нужны нахрен, полей будет достаточно.


          1. fse
            04.01.2022 19:47
            -1

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

            int process(int arg, int *res1 = nullptr, int *res2 = nullptr)

            в случае, если результаты не всем и не всегда нужны. Пример из qt:

            QString::toFloat(bool *ok = nullptr)

            Но мой подход не эталонный, у меня сишная деформация.

            А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!


            1. mayorovp
              04.01.2022 20:57
              +3

              А что там нечитаемого-то? Сравните:


              auto [x, y, z] = process(arg);
              
              int y, z;
              int x = process(arg, &y, &z);

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


              Что же до примера из Qt — сейчас такие вещи через std::optional делать рекомендуется.


              А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!

              Нахер такого советчика, и всё.


              1. gecube
                04.01.2022 20:59
                -1

                В С++ варианте со ссылками же не надо писать уродские &?

                int process(int arg, int &y, int &z);
                
                ...
                
                int y, z;
                int x = process(arg, y, z);

                профит налицо?


                1. datacompboy
                  04.01.2022 21:00
                  +4

                  И совершенно непонятно что y и z -- это возвращаемые праметры.


                1. mayorovp
                  04.01.2022 21:31
                  +1

                  И параметры сразу же перестали быть опциональными.


                  Вариант же с tuple позволяет получить только одно значение при желании:


                  int y = process(arg).get<1>();


                  1. gecube
                    04.01.2022 21:47

                    И параметры сразу же перестали быть опциональными.

                    к сожалению (((

                    А еще "бест-пректис" использовать varargs :-) https://www.gnu.org/software/libc/manual/html_node/Variadic-Functions.html Но это тащить в С++ точно не стоит


                  1. fse
                    04.01.2022 23:32
                    +1

                    gcc с флагом --std=c++17 почему-то ваш пример обругал ’has no member named ‘get’. Только так уговорил:

                    std::get<1>(process());

                    Не в пользу туплов здесь представляется несколько аргументов:

                    • Получаем только 1 аргумент, либо все сразу

                    • Указание аргумента по номеру, а не по имени

                    • Код tuple<int,int,int> не самодокументирующий

                    • Structured binding declarations пока некорректно воспринимается большинством редакторов (подчёркивает, код-комплит не работает)

                    • Анархия вызовов при -O0, сложно делать отладку.

                    • Не годится, если важен zero-overhead (копирований на стеке не избежать).

                    • Обращение к самым последним стандартам (has no member)

                    С учётом сказанного, предпочёл бы выбор "паковать в класс" либо "писать по указателю".


            1. F0iL
              05.01.2022 00:51

              фу, богомерзкие голые поля класса

              Объявите возвращаемый результат как struct вместо class и даже для "эстетов" это будет выглядеть вполне естественно :)


              1. fse
                05.01.2022 01:08

                Быть структурой для истинного эстета не является уважительной причиной не внедрять в неё методы ;)


  1. tbl
    04.01.2022 03:26
    +9

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


    1. BeardedBeaver
      04.01.2022 21:10
      +2

      Наткнулся на такую хрень в реальном проекте недавно. Пытаться раскрутить зависимости и найти нужные файлы это крайне увлекательно. (Не) рекомендую.


      1. tbl
        04.01.2022 22:20
        +1

        Другая крайность: инклудить все подряд, ведь ты заранее не знаешь, насколько развернется полет твоей мысли, поэтому в дефолтном шаблоне для .h-файлов надо заранее включить

        #include <stddef.h>
        #include <windef.h>
        #include <Qt>

        Ведь эти файлы наверняка есть на всех платформах


        1. fse
          05.01.2022 00:15
          +5

          #include "calc.cpp"
          #include "lib.cpp"

          Лечите ошибки линкера эффективно!

          #include "/usr/include/lib.h"
          #include "../../vasya/project/library.h"

          Не оставляйте неоднозначность!


    1. LynXzp
      05.01.2022 02:54

      они зависят от других заголовков, при этом не включайте их
      • Если используете какую-то библиотеку — вовсе не обязательно ее добавлять в сборочные файлы. И уж точно не нужно фиксировать ее версию. Это отсечет новичков от которых одни проблемы.


  1. souls_arch
    04.01.2022 03:30
    +5

    К 4 пункту помимо 2-3 буквенных обозначений названий переменных можно смело добавлять:

    1) с названиями классов и методов(функций) делайте то же самое

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

    3) если вы даете названия переменным, функциям и классам на англо-бурятском, потому что английский у вас - твердый а1, не лезьте никогда в словарь, чтобы проверить как пишется слово или словосочетание, устойчивое выражение. Если среда разработки ругается на орфографию - отключите. Так вас вас поймет максимальное число и русских и иностранных разработчиков, - вы ведь только изучаете английский, тренируя память и придумывая новые бессмысленные выражения.

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


    1. third112
      04.01.2022 07:22
      +2

      Очень хороший вредный совет давать имена на надсат, пример:


      soxranitinapechatatresultat


      Все, кто будет работать с Вашим кодом, получат яркие впечатления!


      1. MAXH0
        04.01.2022 09:20

        Зато когда фрагмент вашего кода с Гитхаба используют для взлома Пентогана, у ЦРУ появятся "неопровержимые доказательства


      1. fse
        05.01.2022 01:18

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


        1. third112
          05.01.2022 01:33
          +1

          Не понял: о каком протоколе речь (FTP, TCP,..)? Пожалуйста, подскажите RFC. Или м.б. это ГОСТ, копирующий международный стандарт? Как в США, Европе, Японии, Китае и т.д. работают с ру-протоколом, не зная русский?


          1. fse
            05.01.2022 02:18

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


            1. third112
              05.01.2022 02:58

              Конечно, бывают исключительные случаи. Но ИМХО тут помогут комментарии, которые можно писать на любом языке. Мой пример:


              soxranitinapechatatresultat

              Можно записать:


              SPR, // save & print result — сохранить и напечатать результат


              Транслитерация не всегда однозначна. Такие буквы, как щ, ш, э, ъ, я, ч, ы часто переводят по-разному. И возникают ошибки.


              1. fse
                05.01.2022 03:49

                Пробовал и через комментарии. Тратил очень много времени на корректный перевод технических терминов и правильное именование (это мне впоследствии помогло хорошо ориентироваться в импортной литературе, так что не зря). Проблема такого подхода в необходимости постоянно "подглядывать" в комментарии чтобы вспомнить имя какого-нибудь поля с нераспространённым или уникальным названием. В то время как русскоязычная аббривиатура постоянно наслуху. Другая проблема в отличии понятийных баз. Резюмируя, признаюсь, что конкретно в этом случае я не ругаюсь на коллег за транслитерации. Если вам будет интересно - пишите в личку - дам примеры, которые лучше пояснят глубину проблемы(?) в одной конкретной отрасли.


  1. masai
    04.01.2022 04:01
    +7

    • Зачем нужны все эти *_cast, если есть reinterpret_cast, который всегда работает?

    • Стек не резиновый! Не нужно делать много вызовов функций. В идеале нужно вообще всё в main уместить.

    • Если всё же пришлось написать функцию, то она должна быть мощной и универсальной, как швейцарский армейский нож, и должна принимать много аргументов. Для экономии времени можно аргументы не перечислять, а парсить с помощью va_arg.

    • C и C++ — это практически один и тот же язык. Поэтому используйте лучшее из двух миров. Если в одной функции удобнее использовать printf, а в другой cout, то надо использовать то, что удобнее.

    • getch() в конце main() — это правило хорошего тона при написании консольных программ.

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

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

    • Зачем проверять успешность ввода/вывода? SFINAE же, std::ios_base::failure is not an error.

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

    • Какой ещё DRY? WET! Write everything twice!

    • Никакая система сборки не знает ваш код лучше вас. Лучше напишите простой скрипт, который компилирует всё шаг за шагом. Ну да, длинно, зато надёжно.


    1. Amomum
      04.01.2022 04:35
      +7

      Зачем нужны все эти *_cast, если есть reinterpret_cast, который всегда работает?

      Пфф, зачем так много печатать, если есть С-style-cast?


  1. masai
    04.01.2022 04:15
    +7

    • А вы знаете, что вместо фигурных скобок использовать <% и %>? Диграфы и триграфы могут придать вашему коду визуальную свежесть и необычность, выделит его на фоне кода коллег. И при этом ничего незаконного, они же есть в стандарте.

    • Зачем инициализировать переменные, если там и так нули? Я вот недавно не инициализировал и там ноль был. Всё работало.

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

    • Пишите код так, как будто его будет читать председатель жюри IOCCC, и он знает, где вы живёте (чтоб приехать и вручить вам приз).

    • Если в C++ переносы строк и отступы незначимые, то почему бы не оформить код в виде зайчика или белочки?


    1. third112
      05.01.2022 03:12

      Думаю, зайчик и белочка будет оригинально и забавно! Но если кто после Вас будет делать в код вставки, но у него не будет художественного таланта, как у Вас, то он разрушит Ваше художество. — Будет обидно.


      1. masai
        05.01.2022 15:05

        Сломает — не пройдёт ревью. :)


  1. masai
    04.01.2022 04:31
    +3

    • Если программа компилируется, значит все ОК. Это ж C++, тут типы и проверки!


  1. AnthonyMikh
    04.01.2022 07:53
    +5

    Самый главный вредный совет: пишите на C++, используйте его!


    1. Blooderino
      04.01.2022 10:37
      -3

      Да-да, все уже давно поняли, что либо пишите на питоне, либо вообще не программируйте. Успокойся уже.


      1. 0xd34df00d
        04.01.2022 10:52
        +9

        Зачем питон, когда есть, в зависимости от ваших вкусов, хаскель или раст?


      1. AnthonyMikh
        04.01.2022 21:08
        +2

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


    1. Wolf4D
      04.01.2022 13:52
      +1

      Лучше так - пишите ВСЁ на C++:

      • Надо автоматизировать что-то в *никсах? Собственная программа на C++ сделает это лучше всяких неведомых Баш-скриптов. А то всякие пайпы ещё придумали, изверги...

      • Распарсить ответ от сайта? Зачем Питон? Ведь это надо учить ещё один язык. Ещё великий Брюс Ли боялся не того, кто повторил тысячу ударов - а того, кто повторил один удар тысячу раз!

      • Сделать сайт? Вы будете смеяться, но с современными технологиями можно компилировать C++ в Web Assembly. Ведь в термине "веб-приложение" главное слово - это "приложение". А приложения, как известно, пишутся на C++.

      • Android-разработка? Что значит "вся платформа построена вокруг Java"? У нас есть NDK, у нас есть Qt. А если кому-то не нравится, как выглядит результат - может закрыть глаза.


      1. MAXH0
        04.01.2022 17:09

        Четвертое: Зачем закрывать глаза? Они сами вытекут!


  1. Oval
    04.01.2022 08:38
    +4

    1. Используйте числа в программировании. Так ваша программа будет выглядеть умнее и солиднее. Согласитесь, что такие строки смотрятся хардкорно: qw = ty / 65 — 29 * s;

    i = 0x5f3759df - (i >> 1);


  1. MAXH0
    04.01.2022 09:16
    +3

    Оформлять стандартный код через устоявшиеся идеомы - тривиально. Разнообразьте написание кода. Пусть читающий ваш код размышляет и компилирует код у себя в голове, а не просто видит знакомые фрагменты текста.

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


  1. gorlov-admin
    04.01.2022 10:45

    Новый совет: не трать время на документацию и пояснения. Код перед глазами. Разберемся.


  1. AntiVassal
    04.01.2022 11:50
    +1

    Однажды попал на туториал по С++ на Ютубе, где спикер пользовался 8-ым советом, когда пытался получить доступ к десятому элементу массива длинной десять.


    1. unsignedchar
      04.01.2022 11:55
      +1

      спикер пользовался 8-ым советом

      Это если с 0 считать или с 1? ;0


      1. AntiVassal
        04.01.2022 13:05

        Он считал с нуля, и обратился к одинадцатому элементу (с индексом 10)


        1. pi3142
          05.01.2022 12:03

          Это шутка про то, откуда начать отсчёт совета)


  1. maxood
    04.01.2022 13:14
    +1

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

    Если речь о size_t или unsigned int, то многие не согласятся с этим пунктом. STL за это критиковали многие гуру C++. Eigen, например, использует именно int.


    1. Andrey2008 Автор
      04.01.2022 17:23
      +1

      unsigned int / int может как-бы быть недостаточно…


      1. maxood
        04.01.2022 18:44
        -2

        ОК, Alexandresku и Stroustrup заблуждались.


        1. maxood
          04.01.2022 19:24
          -1

          специально для минусаторов - https://www.youtube.com/watch?v=Puio5dly9N8#t=42m40s


        1. Andrey2008 Автор
          04.01.2022 19:41

          Что такое size_t и ptrdiff_t. Это моё повествование несколько устарело, но тем не менее.


      1. maxood
        04.01.2022 18:59

        можно начать с этого - https://github.com/ericniebler/stl2/issues/182

        "If you need an extra bit, I am really reluctant to believe you" - классика :)


      1. fse
        04.01.2022 19:14

        Также QT придерживается int-стиля.

        unsigned int / int может как-бы быть недостаточно…

        Смотря для чего. В моей практике MAX_INT в 99,9% случаев заведомо достаточно (если забыть о существовании некоторых платформ с разрядностью < 32 бит).

        Могу привести такой контраргумент на тему достаточности. Если Вы допускаете, что у вас, предположим, длина вектора может быть size_t (то есть на 32-битных платформах не 2^31, а 2^32 элементов), то я ожидаю, что ваш код адаптирован к работе с большими массивами памяти (от 4 Гб и выше). На самом деле почти всегда это не так. Подобная адаптация - это очень серьёзный и очень частный для конкретной задачи вопрос. Чтобы это понять, попробуйте произвольный вектор в своей программе забить 2^32 элементами и расскажите что получится (желательно не vector<uint8_t>, а что-нибудь позабористей). Если программа не упадёт по нехватке памяти, то обратите внимание на производительность и проверьте загрузку винта, на который пишется SWAP. Потом выключите свапирование, т.к. полагаться на него не очень правильно.


        1. Playa
          04.01.2022 21:26
          +1

          В qt6 уже используется qsizetype (=ptrdiff_t)


          1. fse
            04.01.2022 22:32

            Да. При этом ptrdiff_t = ssize_t. Оставляя знаковый бит, авторы не погнались за тем "чтобы было достаточно", ограничившись 2^31 на 32-битных платформах (или 2^63 на 64-битных). Это похоже не компромисс между аргументом в пользу адресной арифметики и в пользу организации знаковых циклов for.


            1. tbl
              04.01.2022 22:57
              +3

              Да нет, это полечит боль тем, кто пишет

              for (int i = arr.length(); i--; i>=0)

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


              1. unC0Rr
                05.01.2022 13:05
                +2

                Ну как же так!

                arr.length() - 1


                1. tbl
                  05.01.2022 13:08

                  Простите, опять off-by-one


            1. xael
              06.01.2022 00:31

              http://advsys.net/ken/add-128.htm

              Может быть используют signed int ещё и по этой причине.


  1. axe_chita
    04.01.2022 15:59

    Григорий Остер — Вредные советы для пОгроммистов? Готов купить если выйдет на бумаге!:)


  1. gecube
    04.01.2022 19:28
    +3

    Используй самый древний и самый старый стандарт С++, который сможешь найти. Пускай твои коллеги удивятся и дадут тебе медальку ачивку Археолог. Зато твой код сможет скомпилироваться на любом компиляторе - они ж должны поддерживать старые стандарты?

    Либо... используй самый новый стандарт и все те возможности, которые в него вошли как draft... Покажи всем какой ты крутой джедай и как ты хорошо знаешь эти bleeding edge фичи. Не смотри на коллег - они глупцы, пускай пишут на мейнстриме, ты же крутой Нео, Йода си плюс плюса.


  1. AnonimYYYs
    04.01.2022 20:41

    А можно, пожалуйста, пояснения насчет 3 и 5.

    Если насчет 3 еще вроде есть мысли, то с 5 вообще не понимаю как исправлять и что в принципе не так.


    1. Andrey2008 Автор
      04.01.2022 20:46

      3 — pvs-studio.com/ru/docs/warnings/v550
      5 — именованные константы + желательны комментарии, поясняющие формулы


      1. masai
        05.01.2022 00:56

        Кстати, сравнивать через fabs(a - b) < epsilon — это тоже может быть не лучшим решением. Вот здесь разбирается одно из решений.

        Кнут предлагал такую формулу:

        fabs(a - b) <= ((fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon)


        1. tbl
          05.01.2022 01:32

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


        1. F0iL
          05.01.2022 12:41
          +1

  1. IntActment
    05.01.2022 04:39

    ・зачем париться с инклюдами? Создайте один мега-header и заинклюдьте в него всё, что может потенциально пригодиться! И в самое начало не забудьте добавить

    #include <windows.h>
    и
    using namespace std;
    чтобы два раза не вставать!


  1. a-tk
    05.01.2022 12:14
    +3

    Не забывайте делать вместо простых функций макросы. Это работает быстрее. Тем более никогда не проходите мимо min и max. Любой проект должен начинаться с определения этих макросов.


  1. AndrewChe
    05.01.2022 12:54
    +2

    Не у каждого человека есть система сборки, поэтому вместо одного скрипта просто помести в readme все команды для компилятора.

    Тестировать на других системах? Зачем, если у тебя все работает.


  1. Restorator
    05.01.2022 14:03
    +3

    n+1: Все знают, что операторы обращения по индексу к указателю обладают коммутативностью. Такой код удобно читать и проще отлаживать. Коллеги будут благодарны.

    1[ptrA] = 8;
    1[ptrB] = 9;
    1[ptrCustom] = 10;

    n+2: Тру программисты знают, что деление на 2 можно заменить на битовые сдвиги. Используй их и твой код будет быстр, как в релизе, так и в дебаге!

    a >>= 2;

    n+3: В умных книгах пишут, что кэш-миссы очень опасны. Поэтому итерироваться надо сперва по Y, потом по X и строго в обратном направлении.

    for (int j = sizeY - 1; j >= 0; --j)
        for (int i = sizeX - 1; i >= 0; --i)
            j[i[ptr]] <<= 2;


    1. Izaron
      05.01.2022 15:26

      Да уж, это насколько же программисту надо плохо думать о разработчиках компиляторов, чтобы полагать, что x /= 2 не заменится на x >>= 1 сам)


      1. F0iL
        05.01.2022 15:37

        так поэтому и сказано

        код будет быстр, как в релизе, так и в дебаге


      1. emaxx
        05.01.2022 16:33
        +2

        Будете смеяться, но не заменится! Хоть с -O2, хоть -O3. Потому что не имеют права, в случае если переменная signed (если оптимизатор не может доказать, что значение на самом деле всегда неотрицательно).

        Пруф: https://godbolt.org/z/EGYeT3nj1


      1. alliumnsk
        05.01.2022 16:34
        +2

        -3 >> 1 /* дает -2 */
        -3 /2 /* дает -1 */
        Компилятор обычно не может доказать что там не могут попасться отрицательные нечетные числа, для которых эта операция дает другой результат


  1. speshuric
    05.01.2022 14:24

    Перегрузите как можно больше операторов.

    Перегрузите как можно больше операторов, в том числе и не арифметические.

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

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

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


  1. yeputons
    06.01.2022 17:01
    +1

    • Считайте, что C++ - надмножество Си и писать надо почти так же.

    • Следуйте рекомендациям с https://twitter.com/affectivecpp , например:

      • Так как UB используется компиляторами для оптимизаций, убедитесь, что в вашем коде как можно больше UB, чтобы программа быстрее работала.