Я пишу статьи, посвященные написанию качественного кода и про поиск ошибок с помощью инструментов статического анализа. Однообразие наскучивает, хочется пошалить. А давайте все вместе напишем статью "100 вредных советов для С++ программиста". Я начну, а вы подхватите.
- Всюду используйте вложенные макросы. Так текст программы станет короче, и вы сохраните больше места на жестком диске. Заодно это развлечёт ваших коллег при отладке.
- Если в строковом литерале вам нужен символ табуляции, смело жмите кнопку tab. Оставьте \t для яйцеголовых. Не парься.
- Смело сравнивайте числа с плавающей точкой с помощью оператора ==. Раз есть такой оператор, значит им нужно пользоваться.
- Используйте для переменных имена из одной — двух букв. Так в одну строчку, помещающуюся на экране, можно уместить более сложное выражение.
- Используйте числа в программировании. Так ваша программа будет выглядеть умнее и солиднее. Согласитесь, что такие строки смотрятся хардкорно: qw = ty / 65 — 29 * s;
- Отключи предупреждения компилятора. Они отвлекают от работы и мешают писать компактный код.
- Не мешкай и не тормози. Сразу бери и используй аргументы командной строки. Например, так: char buf[100]; strcpy(buf, argv[1]);. Проверки делают только параноики, неуверенные в себе и в людях.
- Если что-то не работает, то, скорее всего, глючит компилятор. Попробуй поменять местами некоторые переменные и строки кода.
- Undefined behavior это страшилка на ночь для детей. На самом деле его не существует. Если программа работает как вы ожидали, значит она правильная. И обсуждать здесь нечего, точка.
- memmove — лишняя функция. Всегда и везде используйте memcpy.
- Помни, что размер указателя это всегда 4 байта. Используй смело это число. Число 4 смотрится намного изящнее, чем корявое выражение с оператором sizeof.
- Нет смысла проверять, удалось ли выделить память. На современных компьютерах её много. А если не хватило, то и незачем дальше работать. Пусть программа упадёт. Все равно уже больше ничего сделать нельзя.
- Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа int. Так и делай. Не стоит нарушать традиции.
- Используй при написании кода невидимые символы. Пусть ваш код работает магическим образом. Это прикольно.
- Добавляй разные вспомогательные функции и классы в пространства имён std. Ведь для тебя эти функции и классы стандартные и базовые, а раз так, им самое место в std.
Оставляйте комментарии, а позже оформлю всё это в виде единого текста. Думаю, будет прикольно. Присоединяетесь!
Комментарии (163)
horror_x
03.01.2022 23:16+10Используйте меньше фигурных скобок и переносов строк, старайтесь писать условные конструкции в одну строку. Так код будет быстрее компилироваться и занимать меньше места.
gregorybednov
04.01.2022 14:08В целом да, но при специфическом кодстайле по-моему возможны исключения.
Если почти весь код выстроен вокруг проверок и действий по проверке (проверить аргумент и выйти из функции с кодом возврата ошибки или скажем изменить значение счётчика при условии), на мой взгляд вполне оправдано пользоваться условием как раз без переноса строки и фигурных скобок:
if (cond) return handler();
Аналогия с командами по предикату - в ассемблере x86 это условные переходы, только тут это будут возвраты в функции, в ассемблере ARM для предикатов существует, если не ошибаюсь, полноценная система исполнения на любой команде.
Причем важно, что лучше всего запись именно без переноса строки и фигурных скобок одновременно. Фигурные скобки тут просто мешают читаемости, а перенос строки и написание кода "а-ля Python" может очень сильно обмануть, если вдруг придётся тело условия расширить.
Ну а "стандартный" вариант в стиле классического С это 4 строки, в стиле Java - 3, что очень сильно разреживает код и (имхо) рассредотачивает внимание, т.к. очень серьезно отличается по плотности кода от последовательностей операторов в одном блоке кода (ср.: 1 выражение на 1 строку vs 1 выражение на 3-4 строки).
Отдельно замечу, что циклов этот приём не касается (там лучше помириться с телом на 3-4 строчки, чем проморгать что-то не то)
horror_x
04.01.2022 17:37+1Насчёт исключений согласен. Если можно оформить более читаемо (в виде таблицы, например), то почему бы и нет.
А насчёт проблем с восприятием разреженного кода — это скорей вкусовщина и дело привычки. С однострочными if беда в том, что условие всегда разной длины, и в любом случае приходится искать глазами где оно кончается. А двустрочные if без фигурных скобок просто опасны — очень легко в состоянии потока добавить строчку-другую, забыв про необходимость блока.
Мне приходилось одновременно вести проекты на Java/Go/C++ с разными кодстайлами, и всё-таки вариант, где после if чётко выделялся выделяется блок переносами строк, в итоге воспринимался гораздо лучше. Всё потому, что всё всегда на одних и тех же позициях относительно начала строки, нет необходимости взглядом парсить условие, чтобы найти начало блока или выражения. В итоге такой разреженный код можно осознанно скроллить гораздо быстрее, чем более плотный. Блоки в if'ах выхватываются взглядом моментально.
То же касается разделения логически связанных участков кода пустыми строками — это разгружает мозг при чтении кода. Код более разреженный, но воспринимается гораздо лучше.
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/)
emaxx
03.01.2022 23:27+15Нет смысла проверять, удалось ли выделить память. На современных компьютерах её много. А если не хватило, то и незачем дальше работать. Пусть программа упадёт. Все равно уже больше ничего сделать нельзя.
На самом деле, даже если без шуток, это нормальный совет для большинства программ. Если мы не можем больше выделить нисколько памяти, то сделать что-то осмысленное - очень сложно, а выхлопа - мало, потому что, скорее всего, нам в любом случае надо завершиться. ОК, может быть, в каких-то редких случаях, например, если мы пишем какой-то системно-важный демон или пишем embedded софт, дополнительные усилия могут быть оправданы. Но в среднестатистической программе падать на OOM - это стандартная рекомендация.
Кстати, насколько я знаю, в Rust это стандартное поведение библиотеки - немедленное завершение при OOM. То же делает аллокатор в реализации Chrome.
gecube
03.01.2022 23:33+8Но в среднестатистической программе падать на OOM - это стандартная рекомендация.
проблема только в одном - в линуксе можно аллоцировать сколько угодно памяти, только программе упадет не на ее аллокации, а когда реально выяснится, что ее нет в системе и к тому же просто придет уже системный ООМкиллер и всех порешает ((((
Andrey2008 Автор
04.01.2022 00:02Вредный совет. Всё не так просто. У меня была целая статья про это: Почему важно проверять, что вернула функция malloc.
mayorovp
04.01.2022 00:06+16С malloc-то всё понятно, ежели написано что иногда она таки 0 возвращает — надо бы проверить.
Но мы же про С++ говорим, где используется new.
b2soft
04.01.2022 10:30Дак и new либо швыряет std::bad_alloc, либо в нешвыряющей версии nullptr отдает (безусловно, есть исключения с преопределением new и/или std::set_new_handler...)
mentin
04.01.2022 06:45+2Выше предлагалось падать, а не продолжать работь. Не знаю что имел в виду автор, но это лучше всего делать переопределив malloc на функцию вызывающую exit() если память кончилась. Тогда все приведенные в той статье аргументы не применимы.
alextretyak
05.01.2022 00:00А как обстоит с этим в самой PVS-Studio? Вы проверяете удалось ли выделить память? И что делаете в случае когда не удалось?
ИМХО, самый разумный способ разрулить это на уровне операционной системы, «замораживая» приложения в случае невозможности аллокации памяти, и предоставляя пользователю самому решать что делать: принудительно завершить приложение, либо оставив его «замороженным» закрыть другие приложения [дабы освободить немного памяти], разморозить «замороженное» приложение, сохранить свои данные и закрыть его (чтобы перезапустить).gecube
05.01.2022 00:27-1Плохая, негодная, нерабочая стратегия. Есть же оомкиллер в линуксе. Единственная нормальная стратегия:
брать столько памяти, сколько нужно и ни байтом больше. (С учётом пиковой нагрузки)
Если мы крашнулись по оперативе - сохраняем данные на диск без потери и даём пользователю или демону супервизору перестартовать наше приложение
Все остальное скорее приводит к ещё большим проблемам, чем изначально было
alextretyak
05.01.2022 01:00Ваша стратегия для серверов, а я имел в виду стратегию для десктопов (забыл уточнить просто).
Если мы крашнулись по оперативе — сохраняем данные на диск без потери
Это о каких данных идёт речь?
И что если для сохранения на диск (в случае когда это сохранение выполняется на уровне приложения, а не операционной системы) требуется немного дополнительной памяти, а она кончилась?
Andrey2008 Автор
05.01.2022 00:31Каждый .cpp файл обрабатывает отдельным процессом. Поскольку какие-то пользовательские данные потеряны быть не могут, то исключение std::bad_alloc будет перехвачено и ядро спокойно завершит работу, выдав соответствующее предупреждение. Т.е. какой-то особый файл будет просто не проверен. Поскольку это крайне редкое явление, как-то специально думать про такой сценарий нет необходимости.
А теоретически такое может быть? Да, может. Но это что-то особенное. Про один из таких интересных случаев вы можете услышать в докладе "Не связывайтесь с поддержкой C++ программистов". Это там, где про строковый литерал на 26 мегабайт.
tbl
04.01.2022 03:06+5В дополнение к демону и embedded-софту это так же относится и к библиотекам: никогда на перед не угадаешь, в каком окружении они будут использоваться. Да и валиться из библиотеки в фатальное завершение программы - моветон. Лучше вернуть ошибку, пусть тот, кто библиоткеу использует, решает, как с этим жить (или не жить) дальше.
souls_arch
04.01.2022 03:11В 10 и половина приложений не выдает в журнал что схлопнулось по причине нехватки озу. А можно ведь еще своп раскорячить на много гигов, да на ссд. Как думаете, сколько виндовых приложений из 100 будет его бессовестно жрать и молча причмокивать? Сама 10ка по поводу нехватки озу давно не парится. Не будет вам ни логов информативных ни bsod с перезагрузкой.
mSnus
04.01.2022 04:07+3Вопрос в том, сколько памяти вы захотели выделить. Если вам не дают гигабайт, это ведь не значит, что свободных пол-гига вашей программе не хватит, чтобы сохранить работу пользователя?
Или это паттерн программирования "обидка" -- ах, операция не удалась? ну, всё!
unsignedchar
04.01.2022 09:34+2Если достаточно пол-гигабайта — зачем программмист просил гигабайт?
0xd34df00d
04.01.2022 10:51+1Потому что с лишней памятью может быть быстрее.
unsignedchar
04.01.2022 11:00-1Я так понимаю, программист закодировал только один алгоритм — быстрый, но жрущий память. Возможно, он и не в курсе, что есть и другой способ.
tyomitch
04.01.2022 17:27Я недавно ковырял реализацию
std::vector
, которая при необходимости увеличить буфер пытается увеличить его сразу вдвое, и если не получилось — то пытается увеличить впритык для добавляемых элементов.
А вы бы как написали? При каждом добавлении делали бы реаллокацию впритык?unsignedchar
04.01.2022 17:31+1А вы бы как написали?
В любой неясной ситуации нужно писать свой собственный аллокатор поверх системного ;)
geher
04.01.2022 12:35+1Полгига может быть достаточно не для выполнения той же операции, а, например, для корректного завершения программы с сохранением промежуточных результатов.
Хотя вариант с переходом на более медленный или менее точный алгоритм с меньшим потреблением памяти тоже возможен. Предварительная проверка на доступный объем памяти может не сработать, поскольку память может быть выделена конкурмрующим потоком или процессом между проверкой и собственно выделением.
Еще можно просто подождать, пока память не освободится. Случаи разные бывают.
mSnus
04.01.2022 12:49+8Например, пользователь работает в графическом (текстовом, видео, аудио) редакторе, у него открыты несколько несохраненных файлов, несколько часов работы. И вот он попытался открыть большой файл... Как же он будет вас ненавидеть с походом "пусть падает"!
gregorybednov
04.01.2022 10:38+1Имеет смысл по крайней мере правильно финализировать работу программы - освободить ту память и прочие ресурсы (файлы), которые уже были заняты. В противном случае при первой же попытке работать с такой программой в автоматическом режиме (например, при помощи скрипта на bash), например вызывать её до первого успешного исполнения (то есть до экзит-кода 0), мы легко можем получить проблемы уже в пользовательской системе.
mayorovp
04.01.2022 11:43+4Занимаемую процессом память, как и прочие ресурсы (файлы), освободит ОС.
Если что и нужно финализировать перед падением — так это сбросить буферы вывода.
Kelbon
06.01.2022 08:55+1Финализируют работу программы автоматически вызванные деструкторы. И std::terminate. В случае крайней небходимости std::terminate_handler. Ни в коем случае эта логика не должна стоять над каждым выделением памяти, особенно учитывая что оно ДОЛЖНО быть неявным в современном С++(то есть без явного new)
В итоге для 99% программ достаточно
int main() try {
...
} catch (std::bad_alloc&){
}
firehacker
05.01.2022 05:58+2На самом деле, даже если без шуток, это нормальный совет для большинства программ.
А потом мы видим статьи «Почему в современном софте всё всрато»...
a-tk
06.01.2022 19:23В embedded выделять память после инициализации явно запрещено. А ещё лучше вообще всю память выделять статически, в смысле без динамического выделения вообще.
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).
Skykharkov
03.01.2022 23:44+12От себя лично добавлю. Так сказать из своего опыта.
Никогда не тестируйте. И не пишите тестов. Ваш код идеален, что там тестировать!
Всегда и везде выкатывайте любые изменения сразу на продакшен. Тестовые сервера - лишняя трата денег.
У всех пользователей есть десятигигабитный интернет и четырехпроцессорные ноутбуки. 21-й век на дворе все таки.
Любой заказчик рад оплачивать ваши счета, даже если вы ничего не делаете. Просто шлите больше счетов.
Всегда, при любых ситуациях, используйте как можно большее количество вложенных друг в друга объектов. Сложность - это надежно! Сложность - это солидно!
Никогда не используйте платные компоненты. Только ломанные и с как можно более "левых" иранских и пакистанских сайтов. За что этим бездельникам платить деньги, в конце концов.
Общайтесь с заказчиками с помощью онлайн переводчиков. У нас слова вылетели. Проблема с пониманием на той стороне.
Не делайте бакапы. Вы всегда сможете, за пару дней, по памяти, восстановить проект, который был в разработке больше года.
Чтение документации вредит вашему здоровью. Что, эти жалкие фигляры, там могут написать такого что вы не знаете.
tbl
04.01.2022 03:15+4В дополнение к отсутствию тестов: если приходится тестировать, то мокайте все подряд (даже соседние классы, которые вы же и написали), ведь только так вы сможете смоделировать удобное вам поведение окружения, чтобы тесты смогли проходить идеально. Пусть реальное окружение будет отличаться от моков: чем противоречивее, тем лучше, ведь только так вы сможете доказать, что ваш код идеален, а окружающая действительность должна подстраиваться под ваш код.
LynXzp
05.01.2022 02:36+1Не пользуйтесь системой контроля версий. Храните исходники прямо на сервере в виртуалке.
stan_volodarsky
04.01.2022 00:00+3С третьим пунктом не согласный я. Есть ситуации в которых сравнение вещественных чисел на равенство - осмысленное и хорошо определённое действие.
emaxx
04.01.2022 01:40+5Только надо быть осторожным, потому что даже безобидные вещи вроде "double y=f(x); ... if (y==f(x))" могут работать неправильно (из-за промежуточных вычислений в 80-битных регистрах, насколько я понимаю).
Но согласен, что в некоторых случаях, например, бинарном или тернарном поиске, сравнение через эпсилон только навредит.
stan_volodarsky
06.01.2022 00:02Тот пример, что вы привели - это вариант обмена корректности программы на скорость исполнения. Выигрыш обычно не велик, баги чудовищны.
Я привык думать что "детерминированные вычисления детерминированы". Нарушение этого правила - жуткая головная боль.
Прямо сейчас я работаю с программой у которой "плавают" результаты. Команда разводит руками - "это же вещественные числа, они считают примерно". Соответственно, никто не хочет привести код в порядок. Ррр.
Ах, да, вывод: приведите компилятор в соответствие с IEEE-754, имейте представление когда вычисления с плавающей запятой точные, когда нет (из-за округления) и будет вам счастье.
Ещё надо с утра повторять десять раз: "Эпсилон - часть входных данных. Я никогда не буду вписывать фиксированный эпсилон в код моей программы.".masai
06.01.2022 00:19+1приведите компилятор в соответствие с IEEE-754
Этого мало. При вычислениях с плавающей точкой важен, например, порядок вычислений. Это ещё и оптимизации надо отключать.
Битва за детерминизм может стоит большого количества времени (если вообще достижим, так как сами входные данные часто неточные), и нужно очень хорошо понимать, зачем это нужно и оправданно ли это. Обычно хватает просто устойчивости алгоритма, то есть чтобы при малых изменениях входных данных были малые изменения результата.
Но всё это, конечно, зависит от задачи. Универсального совета нет.
stan_volodarsky
06.01.2022 09:51+1Никогда не видел чтобы оптимизатор переупорядочивал вещественные операции. Именно по причине неассоциативности операций из-за округления. Покажите пример.
masai
06.01.2022 15:17+1Я имел в виду `-funsafe-math-optimizations` (его включает `-ffast-math`). Но вообще, я согласен, мой комментарий не совсем корректный, так как этот параметр по умолчанию выключен (собственно, это требование IEEE754-2008, чтобы по умолчанию не было меняющих значение оптимизаций).
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++-код с плавающей запятой действительно скомпилируется в ожидаемую последовательность точных вычислений.
Woodroof
05.01.2022 23:24+3И, к сожалению, встречал, что используют "нельзя сравнивать вещественные" как мантру, не разбираясь. И эта статья этому только способствует :)
Если числа не являются результатом вычислений, то сравнивать можно и нужно.
ncr
04.01.2022 00:22+11- Не пользуйтесь стандартной библиотекой языка. Что может быть интереснее, чем написать свои строки и списки с уникальным синтаксисом и семантикой?
- Смартпоинтеры и прочее RAII от лукавого, всеми ресурсами надо управлять вручную, это делает код простым и понятным.
- И вообще, выделение памяти — зло.
char c[256]
хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае на 1024. - Глобальные переменные очень удобны, т.к. к ним можно обращаться отовсюду.
Так неинтересно, список можно продолжать бесконечно.
masai
04.01.2022 03:29+2Что может быть интереснее, чем написать свои строки
Тем более, даже Яндекс так делает. :)
управлять вручную, это делает код простым и понятным
Выделяешь и всё. Когда процесс завершится, система сама освободит всё.
unsignedchar
04.01.2022 09:41+1Выделяешь и всё. Когда процесс завершится, система сама освободит всё.
В копилку вредных советов.
Потом этот код либо кто-то вызовет в цикле, либо освободит память 2 раза ;)masai
04.01.2022 15:15В копилку вредных советов.
Да, совет про то, что не нужно освобождать память, вредный, конечно же. Надеюсь, никто не подумал обратного. :)
unsignedchar
04.01.2022 15:52Когда вся программа состоит из 1 функции main() и предназначена для олимпиады — why not.
masai
04.01.2022 21:44Да, я же знаю про неудаляющий аллокатор, но олимпиады — это всё же особая ветвь программирования. :)
datacompboy
04.01.2022 00:27+4я бы дополнил 4е правило: используйте простую систему именования переменных! зачем выдумывать разные? просто берём: int o, O, oo, oO, o0, Oo, OO, O0.
wadeg
04.01.2022 02:54+11Так шутили еще наши дедыНачотдела приходит к своим программисткам:
— Что у вас за код, смотреть противно. Даже идентификаторы: a, aa, aaa, a1, a2, a3… Так сейчас никто не пишет, все используют длинные мнемонически имена. Ясно?
— Ясно!
Заглядывает через неделю. Весь код переписан, везде переменные ДлинноеМнемоническоеИмя1, ДлинноеМнемоническоеИмя2, ДлинноеМнемоническоеИмя3…
LynXzp
05.01.2022 02:39Мне больше нравится использовать двухбуквенные имена переменных, только если закончатся все однобуквенные.
DimanDimanyich
05.01.2022 20:02если бы не ограничения на наименование переменных , то было бы ваще супер!
unsignedchar
05.01.2022 20:13Есть perl с его прекрасными переменными $- $+ $* $.… Жаль что в C++ так нельзя.
Racheengel
04.01.2022 00:29+11Добавляйте в код как можно больше шаблонов и наследуйтесь от них как можно чаще. Ведь лучше написать один раз вложенные десять шаблонов, чем создать десять отдельных классов. Да и код с шаблонами смотрится профессиональней. А современные компиляторы всё равно очень быстрые. А если была где то допущена ошибка, то компилятор выдаст очень ясное и очевидное сообщение о том, что не так и где проблема.
Mike_666
04.01.2022 03:07+3Не забывайте приправлять их макросами.
unsignedchar
04.01.2022 09:48+4Но макросы должны конфликтовать с чем-то. Иначе не интересно будет. Хорошая идея — позаимствовать систему именований из ядра Linux — там много красивых имён. Кто будет портировать код с такими именами — обхохочется ;).
Shaz
04.01.2022 00:59А давайте все вместе напишем статью "100 вредных советов для С++ программиста". Я начну, а вы подхватите.
Лучше потом книгу выпустите. С примерами.
SShtole
04.01.2022 01:32+9Некоторые советы очень даже ничего.
Всюду используйте вложенные макросы. Так текст программы станет короче, и вы сохраните больше места на жестком диске. Заодно это развлечёт ваших коллег при отладке.
Например, так:::SetWindowText(hWnd, _T("Text"));
Вложенный макрос.Если в строковом литерале вам нужен символ табуляции, смело жмите кнопку tab. Оставьте \t для яйцеголовых. Не парься.
Я бы вообще два раза подумал, прежде, чем хардкодить табуляцию в литерале. Хотя бы потому, что слеш легко перепутать с бэкслешем. Это про «технические» литералы, ресурсные лучше выносить в ресурсы.
Кстати, всегда включаю Show visual spaces.Используйте для переменных имена из одной — двух букв. Так в одну строчку, помещающуюся на экране, можно уместить более сложное выражение.
Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни».Sixshaman
04.01.2022 18:07Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни
А в какой статье? Интересно почитать, т.к. сам стараюсь вместо i/j/k использовать itemIndex.SShtole
04.01.2022 18:26С ходу не могу найти, к сожалению. Цитировал по памяти. Я смутно вспоминаю, что там речь шла про собеседования, но это не The Guerrilla Guide. Если позже найду, обязательно отпишусь.
emaxx
04.01.2022 18:48Google 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
masai
04.01.2022 21:50+3Если именно itemIndex, то это ничем не лучше i. Использовать i/j/k для индексов — это общепринятая практика, то есть, когда видишь i, то и так понимаешь, что это item index. Длинное название новой информации не даёт, а читать труднее.
Sixshaman
04.01.2022 21:58-1Во вложенных циклах гораздо читаемее и приводит к меньшему числу ошибок. Я не смогу пересчитать, сколько раз я случайно использовал j вместо i и наоборот.
Amomum
04.01.2022 01:41+10Выравнивание и единый стиль - для слабаков, которые не хотят вчитываться в ваш код. Заставьте их!
Побольше кода в заголовочных файлах (а лучше - весь в одном!), ведь так гораздо удобнее, а время компиляции возрастает очень незначительно.
Злые языки говорят, что goto считается вредным, но это чушь. Этот оператор очень мощен и способен заменить почти все другие конструкции языка, старайтесь пользоваться им почаще. А для переходов между функциями используйте setjmp/longjmp
Никогда не используйте enum'ы, они все равно неявно приводятся к int; используйте int напрямую!
Старайтесь писать документацию как можно подробнее, каждый метод, каждая функция должны быть снабжены развесистым комментарием. Но никогда не давайте примеров использования в документации, это дурной тон! Вы же не считаете пользователей вашего кода дураками?
Используйте как можно больше разных систем сборки и пакетных менеджеров, покажите, что вы в курсе современных тенденций! Разумеется, версии кода в пакетах для разных менеджеров должны быть немного разными, иначе пользователи заскучают.
Вы ведь слышали, что стандартные примитивные типы в С и С++ не имеют гарантированной длины в битах? Чтобы справится с этой проблемой лучше всего завести свои псевдонимы для примитивных типов, с указанной фиксированной длиной. Ни в коем случае не пользуйтесь типами из
stdint.h
, ведь существование этого файла не гарантируется стандартом, а значит, ваш код не будет действительно переносимым!Проявите немного уважения к программистам прошлого, объявляйте все переменные вначале функций. Это - традиция!
Использовать венгерскую нотацию - тоже хорошая, проверенная временем практика; посмотрите в конце концов на WinAPI
KoCMoHaBT61
04.01.2022 03:02-6Всегда и везде используйте enum'ы. #define для слабаков. Постарайтесь как можно чаще использовать enum'ы с разрывами. Также, для строгой типизации не используйте bool, а используйте enum MyFlagType { Yes, No } или enum MySecondFlagType { Maybe, No, Yes } Так приятно, когда человек, отсчитывая по строчке от начала разрыва узнаёт, что загадочное число 27543 в логе означает MSG_CLOSEALL.
Amomum
04.01.2022 03:04+8.. хм, в принципе, я реально предпочел бы enum вместо дефайнов почти всегда. По крайней мере если функция аргументом принимает enum - сразу видно возможные варианты, а вот если просто int - то уповать на документацию или ванговать
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 общего назначения в параметрах нужен весьма редко, например, для интеропа и всяких расширений, а в остальных случаях не надо лениться расшифровывать оба состояния по имени.
masai
04.01.2022 03:42+1А ещё можно switch вместо goto использовать как в Duff's device — ставим метки case на разные части тела одного цикла.
lgorSL
04.01.2022 05:18Если goto использовать нельзя (например, из-за линтера или код-стайла), то можно попробовать цикл
do ... while(true/false)
в сочетании сbreak/continue
.unsignedchar
04.01.2022 09:55А что не так с бесконечным циклом? КМК, использовать его вместо бесконечного цикла вполне нормально.
mayorovp
04.01.2022 11:48Бесконечный цикл с
while(true)
и break — это нормально, называется "цикл с условием посередине".Бесконечный цикл с
while(false)
— это WTF, который допустим лишь в макросах.gecube
04.01.2022 11:53Бесконечный цикл с
while(true)
и break — это нормально, называется "цикл с условием посередине".если это условие выполняется в принципе... хотя по-всякому бывает :-) мало ли это какой-то софт для контроллеров.
lgorSL
04.01.2022 13:57+2do { ... if (smth) break; ... } while(false)
Выглядит как цикл, но по-сути это goto в конец блока. Чтобы понять это, надо дочитать до конца цикла. Особенно весело, когда такие "циклы" идут вперемежку с нормальными.
unsignedchar
04.01.2022 14:05+1Это обычный if, только зачем-то запутанный.
if (! smth) { ...}
SShtole
04.01.2022 15:25+2Предполагается, что их несколько.
unsignedchar
04.01.2022 15:50+1Возможно, это кусок старинного кода, написанного с применением goto. Вот так вот переписанный. Или авторское know-how, чтобы никто не понял, что там происходит.
IntActment
05.01.2022 04:24Это, скорее, вариант быстрого return, чья область действия ограничена одним блоком do {} while вместо всей функции. Причин использования может быть несколько: может быть невыгодно выделять блок с do {} while в отдельную функцию (например, придется передавать слишком много локальных переменных в эту самую функцию), избавление от дублирования кода — иными словами, существенно сокращает количество кода с повышением удобочитаемости.
unsignedchar
05.01.2022 08:46+1Кстати, да. В любом случае, не видя контекста использования, не нужно кидаться тапками, другой вариант может быть ещё хуже.
Izaron
04.01.2022 01:54+6Пиши в хидерах код а-ля
using namespace std;
- коллеги скажут спасибо, за то что им не придется это писать в каждом .cpp!Подключай как можно больше хидеров, чтобы каждый .cpp файл раскрывался в 1mln строк - коллеги скажут спасибо за то, что у них больше времени на перекур во время пересборки!
Функция должна вернуть больше чем одно значение? Есть крутой лайфхак - записывай результаты в указатель! Для трёх значений это
int process(int arg, int* res2, int* res3)
.
gecube
04.01.2022 09:36-4int process(int arg, int* res2, int* res3)
может все-таки:
int process(int arg, int& res2, int& res3)
? по сути то же самое, но немножко более с++ нативно
fse
04.01.2022 18:17-4Подозреваю, что коллеги предлагают написать класс TheProcessFunctionResults... Ну, и, конечно, определить для класса конструкторы, сеттеры, геттеры и, возможно, операторы. Ну а метод TheProcessFunctionResults::stringifyError(), как вишенка на торте, не оставит никаких сомнения у читателя в необходимости наличия этого класса.
Извиняюсь. Сарказм от сишника.
mayorovp
04.01.2022 18:44+1А что сарказм-то? Класс и правда нужен. Правда, можно обойтись
std::tuple<int, int, int>
, но отдельный класс с именованными полями ещё лучше.Аксцессоры только этому классу не нужны нахрен, полей будет достаточно.
fse
04.01.2022 19:47-1Туплы совсем нечитабельно будут выглядеть, на мой взгляд. Против голого класса я бы не поморщился, хотя и предпочёл бы
int process(int arg, int *res1 = nullptr, int *res2 = nullptr)
в случае, если результаты не всем и не всегда нужны. Пример из qt:
QString::toFloat(bool *ok = nullptr)
Но мой подход не эталонный, у меня сишная деформация.
А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!
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 делать рекомендуется.
А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!
Нахер такого советчика, и всё.
gecube
04.01.2022 20:59-1В С++ варианте со ссылками же не надо писать уродские &?
int process(int arg, int &y, int &z); ... int y, z; int x = process(arg, y, z);
профит налицо?
mayorovp
04.01.2022 21:31+1И параметры сразу же перестали быть опциональными.
Вариант же с tuple позволяет получить только одно значение при желании:
int y = process(arg).get<1>();
gecube
04.01.2022 21:47И параметры сразу же перестали быть опциональными.
к сожалению (((
А еще "бест-пректис" использовать varargs :-) https://www.gnu.org/software/libc/manual/html_node/Variadic-Functions.html Но это тащить в С++ точно не стоит
fse
04.01.2022 23:32+1gcc с флагом --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)
С учётом сказанного, предпочёл бы выбор "паковать в класс" либо "писать по указателю".
tbl
04.01.2022 03:26+9Пишите ваши .h-файлы так, что они зависят от других заголовков, при этом не включайте их в свой заголовочный файл. Пусть тот, кто инклудит, догадается, какие заголовки нужно заранее заинклудить перед использованием вашего файла. Чем больше внешних не упомянутых зависимостей - тем лучше, ведь это так увлекательно проходить квест по компиляции проекта
BeardedBeaver
04.01.2022 21:10+2Наткнулся на такую хрень в реальном проекте недавно. Пытаться раскрутить зависимости и найти нужные файлы это крайне увлекательно. (Не) рекомендую.
tbl
04.01.2022 22:20+1Другая крайность: инклудить все подряд, ведь ты заранее не знаешь, насколько развернется полет твоей мысли, поэтому в дефолтном шаблоне для .h-файлов надо заранее включить
#include <stddef.h> #include <windef.h> #include <Qt>
Ведь эти файлы наверняка есть на всех платформах
fse
05.01.2022 00:15+5#include "calc.cpp" #include "lib.cpp"
Лечите ошибки линкера эффективно!
#include "/usr/include/lib.h" #include "../../vasya/project/library.h"
Не оставляйте неоднозначность!
LynXzp
05.01.2022 02:54они зависят от других заголовков, при этом не включайте их
- Если используете какую-то библиотеку — вовсе не обязательно ее добавлять в сборочные файлы. И уж точно не нужно фиксировать ее версию. Это отсечет новичков от которых одни проблемы.
souls_arch
04.01.2022 03:30+5К 4 пункту помимо 2-3 буквенных обозначений названий переменных можно смело добавлять:
1) с названиями классов и методов(функций) делайте то же самое
2) можно использовать в названиях классов, функций, переменных транслит латинскими буквами. Так много русских коллег, читающих ваш код, лучше вас поймут. А на остальных плевать.
3) если вы даете названия переменным, функциям и классам на англо-бурятском, потому что английский у вас - твердый а1, не лезьте никогда в словарь, чтобы проверить как пишется слово или словосочетание, устойчивое выражение. Если среда разработки ругается на орфографию - отключите. Так вас вас поймет максимальное число и русских и иностранных разработчиков, - вы ведь только изучаете английский, тренируя память и придумывая новые бессмысленные выражения.
4) используйте в названиях функций(методов) get и set везде, где вам кажется удобным, даже если это не гэттеры и сэттеры.
third112
04.01.2022 07:22+2Очень хороший вредный совет давать имена на надсат, пример:
soxranitinapechatatresultat
Все, кто будет работать с Вашим кодом, получат яркие впечатления!
MAXH0
04.01.2022 09:20Зато когда фрагмент вашего кода с Гитхаба используют для взлома Пентогана, у ЦРУ появятся "неопровержимые доказательства
fse
05.01.2022 01:18Тут такое дело... Есть протокол, который определяет структуры сетевого обмена. Все поля этих структур (а их сотни) являются аббривиатурами рускоязычных терминов в одной узкоспециализированной области. Шах и мат. Только транслитерация.
third112
05.01.2022 01:33+1Не понял: о каком протоколе речь (FTP, TCP,..)? Пожалуйста, подскажите RFC. Или м.б. это ГОСТ, копирующий международный стандарт? Как в США, Европе, Японии, Китае и т.д. работают с ру-протоколом, не зная русский?
fse
05.01.2022 02:18Нет, речь идёт не о способе передачи информации (TCP/UDP и т.д.), а о составе передаваемой информации. Например, о составе передаваемых данных между автоматизированными изделиями некоторого программно-аппаратного комплекса.
third112
05.01.2022 02:58Конечно, бывают исключительные случаи. Но ИМХО тут помогут комментарии, которые можно писать на любом языке. Мой пример:
soxranitinapechatatresultat
Можно записать:
SPR, // save & print result — сохранить и напечатать результат
Транслитерация не всегда однозначна. Такие буквы, как щ, ш, э, ъ, я, ч, ы часто переводят по-разному. И возникают ошибки.
fse
05.01.2022 03:49Пробовал и через комментарии. Тратил очень много времени на корректный перевод технических терминов и правильное именование (это мне впоследствии помогло хорошо ориентироваться в импортной литературе, так что не зря). Проблема такого подхода в необходимости постоянно "подглядывать" в комментарии чтобы вспомнить имя какого-нибудь поля с нераспространённым или уникальным названием. В то время как русскоязычная аббривиатура постоянно наслуху. Другая проблема в отличии понятийных баз. Резюмируя, признаюсь, что конкретно в этом случае я не ругаюсь на коллег за транслитерации. Если вам будет интересно - пишите в личку - дам примеры, которые лучше пояснят глубину проблемы(?) в одной конкретной отрасли.
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!
Никакая система сборки не знает ваш код лучше вас. Лучше напишите простой скрипт, который компилирует всё шаг за шагом. Ну да, длинно, зато надёжно.
Amomum
04.01.2022 04:35+7Зачем нужны все эти
*_cast
, если естьreinterpret_cast
, который всегда работает?Пфф, зачем так много печатать, если есть С-style-cast?
masai
04.01.2022 04:15+7А вы знаете, что вместо фигурных скобок использовать <% и %>? Диграфы и триграфы могут придать вашему коду визуальную свежесть и необычность, выделит его на фоне кода коллег. И при этом ничего незаконного, они же есть в стандарте.
Зачем инициализировать переменные, если там и так нули? Я вот недавно не инициализировал и там ноль был. Всё работало.
private для параноиков. Кому они нужны, эти поля класса?
Пишите код так, как будто его будет читать председатель жюри IOCCC, и он знает, где вы живёте (чтоб приехать и вручить вам приз).
Если в C++ переносы строк и отступы незначимые, то почему бы не оформить код в виде зайчика или белочки?
AnthonyMikh
04.01.2022 07:53+5Самый главный вредный совет: пишите на C++, используйте его!
Blooderino
04.01.2022 10:37-3Да-да, все уже давно поняли, что либо пишите на питоне, либо вообще не программируйте. Успокойся уже.
0xd34df00d
04.01.2022 10:52+9Зачем питон, когда есть, в зависимости от ваших вкусов, хаскель или раст?
AnthonyMikh
04.01.2022 21:08+2Если бы вы посмотрели мои публикации, то увидели бы, что программировать я очень даже умею, и отнюдь не на питоне.
Wolf4D
04.01.2022 13:52+1Лучше так - пишите ВСЁ на C++:
Надо автоматизировать что-то в *никсах? Собственная программа на C++ сделает это лучше всяких неведомых Баш-скриптов. А то всякие пайпы ещё придумали, изверги...
Распарсить ответ от сайта? Зачем Питон? Ведь это надо учить ещё один язык. Ещё великий Брюс Ли боялся не того, кто повторил тысячу ударов - а того, кто повторил один удар тысячу раз!
Сделать сайт? Вы будете смеяться, но с современными технологиями можно компилировать C++ в Web Assembly. Ведь в термине "веб-приложение" главное слово - это "приложение". А приложения, как известно, пишутся на C++.
Android-разработка? Что значит "вся платформа построена вокруг Java"? У нас есть NDK, у нас есть Qt. А если кому-то не нравится, как выглядит результат - может закрыть глаза.
Oval
04.01.2022 08:38+4Используйте числа в программировании. Так ваша программа будет выглядеть умнее и солиднее. Согласитесь, что такие строки смотрятся хардкорно: qw = ty / 65 — 29 * s;
i = 0x5f3759df - (i >> 1);
MAXH0
04.01.2022 09:16+3Оформлять стандартный код через устоявшиеся идеомы - тривиально. Разнообразьте написание кода. Пусть читающий ваш код размышляет и компилирует код у себя в голове, а не просто видит знакомые фрагменты текста.
И самый хардкор...
Используйте осмысленные имена, которые имеют другой (но почти такой, как заявленый) функционал в коде. Внесение изменений в алгоритм - это не повод переименовать все переменные.
gorlov-admin
04.01.2022 10:45Новый совет: не трать время на документацию и пояснения. Код перед глазами. Разберемся.
AntiVassal
04.01.2022 11:50+1Однажды попал на туториал по С++ на Ютубе, где спикер пользовался 8-ым советом, когда пытался получить доступ к десятому элементу массива длинной десять.
unsignedchar
04.01.2022 11:55+1спикер пользовался 8-ым советом
Это если с 0 считать или с 1? ;0
maxood
04.01.2022 13:14+1Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа int. Так и делай. Не стоит нарушать традиции.
Если речь о size_t или unsigned int, то многие не согласятся с этим пунктом. STL за это критиковали многие гуру C++. Eigen, например, использует именно int.
Andrey2008 Автор
04.01.2022 17:23+1unsigned int / int может как-бы быть недостаточно…
maxood
04.01.2022 18:44-2ОК, Alexandresku и Stroustrup заблуждались.
maxood
04.01.2022 19:24-1специально для минусаторов - https://www.youtube.com/watch?v=Puio5dly9N8#t=42m40s
Andrey2008 Автор
04.01.2022 19:41Что такое size_t и ptrdiff_t. Это моё повествование несколько устарело, но тем не менее.
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" - классика :)
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. Потом выключите свапирование, т.к. полагаться на него не очень правильно.
Playa
04.01.2022 21:26+1В qt6 уже используется qsizetype (=ptrdiff_t)
fse
04.01.2022 22:32Да. При этом ptrdiff_t = ssize_t. Оставляя знаковый бит, авторы не погнались за тем "чтобы было достаточно", ограничившись 2^31 на 32-битных платформах (или 2^63 на 64-битных). Это похоже не компромисс между аргументом в пользу адресной арифметики и в пользу организации знаковых циклов for.
tbl
04.01.2022 22:57+3Да нет, это полечит боль тем, кто пишет
for (int i = arr.length(); i--; i>=0)
P.S.: посылаю лучи проклятья тем, кто придумал этот хабраинлайн-редактор и ни разу не пытался использовать его в реальной работе на мобилках для написания сниппетов кода
xael
06.01.2022 00:31http://advsys.net/ken/add-128.htm
Может быть используют signed int ещё и по этой причине.
axe_chita
04.01.2022 15:59Григорий Остер — Вредные советы для пОгроммистов? Готов купить если выйдет на бумаге!:)
gecube
04.01.2022 19:28+3Используй самый древний и самый старый стандарт С++, который сможешь найти. Пускай твои коллеги удивятся и дадут тебе
медалькуачивку Археолог. Зато твой код сможет скомпилироваться на любом компиляторе - они ж должны поддерживать старые стандарты?Либо... используй самый новый стандарт и все те возможности, которые в него вошли как draft... Покажи всем какой ты крутой джедай и как ты хорошо знаешь эти bleeding edge фичи. Не смотри на коллег - они глупцы, пускай пишут на мейнстриме, ты же крутой Нео, Йода си плюс плюса.
AnonimYYYs
04.01.2022 20:41А можно, пожалуйста, пояснения насчет 3 и 5.
Если насчет 3 еще вроде есть мысли, то с 5 вообще не понимаю как исправлять и что в принципе не так.
Andrey2008 Автор
04.01.2022 20:463 — pvs-studio.com/ru/docs/warnings/v550
5 — именованные константы + желательны комментарии, поясняющие формулыmasai
05.01.2022 00:56Кстати, сравнивать через fabs(a - b) < epsilon — это тоже может быть не лучшим решением. Вот здесь разбирается одно из решений.
Кнут предлагал такую формулу:
fabs(a - b) <= ((fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon)
tbl
05.01.2022 01:32Удобненько. Сразу видно, как программисты пишут для программистов. Никаких подводных камней))
IntActment
05.01.2022 04:39・зачем париться с инклюдами? Создайте один мега-header и заинклюдьте в него всё, что может потенциально пригодиться! И в самое начало не забудьте добавить
и#include <windows.h>
чтобы два раза не вставать!using namespace std;
a-tk
05.01.2022 12:14+3Не забывайте делать вместо простых функций макросы. Это работает быстрее. Тем более никогда не проходите мимо min и max. Любой проект должен начинаться с определения этих макросов.
AndrewChe
05.01.2022 12:54+2Не у каждого человека есть система сборки, поэтому вместо одного скрипта просто помести в readme все команды для компилятора.
Тестировать на других системах? Зачем, если у тебя все работает.
Restorator
05.01.2022 14:03+3n+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;
Izaron
05.01.2022 15:26Да уж, это насколько же программисту надо плохо думать о разработчиках компиляторов, чтобы полагать, что
x /= 2
не заменится наx >>= 1
сам)emaxx
05.01.2022 16:33+2Будете смеяться, но не заменится! Хоть с -O2, хоть -O3. Потому что не имеют права, в случае если переменная signed (если оптимизатор не может доказать, что значение на самом деле всегда неотрицательно).
alliumnsk
05.01.2022 16:34+2-3 >> 1 /* дает -2 */
-3 /2 /* дает -1 */
Компилятор обычно не может доказать что там не могут попасться отрицательные нечетные числа, для которых эта операция дает другой результат
speshuric
05.01.2022 14:24Перегрузите как можно больше операторов.
Перегрузите как можно больше операторов, в том числе и не арифметические.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов, чтобы все операнды и результат были разнотипные.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов, чтобы все операнды и результат были разнотипные, и добавьте туда побчные эффекты.
yeputons
06.01.2022 17:01+1Считайте, что C++ - надмножество Си и писать надо почти так же.
-
Следуйте рекомендациям с https://twitter.com/affectivecpp , например:
Так как UB используется компиляторами для оптимизаций, убедитесь, что в вашем коде как можно больше UB, чтобы программа быстрее работала.
gecube
или \t ? :-)
Andrey2008 Автор
Запутался в символах при оформлении публикации. Fixed :)