Comments 175
Оставьте /t для яйцеголовых. Не парься.
или \t ? :-)
Еще: заменяйте точки с запятой на запятую, тогда будет быстрее работать
В целом да, но при специфическом кодстайле по-моему возможны исключения.
Если почти весь код выстроен вокруг проверок и действий по проверке (проверить аргумент и выйти из функции с кодом возврата ошибки или скажем изменить значение счётчика при условии), на мой взгляд вполне оправдано пользоваться условием как раз без переноса строки и фигурных скобок:
if (cond) return handler();
Аналогия с командами по предикату - в ассемблере x86 это условные переходы, только тут это будут возвраты в функции, в ассемблере ARM для предикатов существует, если не ошибаюсь, полноценная система исполнения на любой команде.
Причем важно, что лучше всего запись именно без переноса строки и фигурных скобок одновременно. Фигурные скобки тут просто мешают читаемости, а перенос строки и написание кода "а-ля Python" может очень сильно обмануть, если вдруг придётся тело условия расширить.
Ну а "стандартный" вариант в стиле классического С это 4 строки, в стиле Java - 3, что очень сильно разреживает код и (имхо) рассредотачивает внимание, т.к. очень серьезно отличается по плотности кода от последовательностей операторов в одном блоке кода (ср.: 1 выражение на 1 строку vs 1 выражение на 3-4 строки).
Отдельно замечу, что циклов этот приём не касается (там лучше помириться с телом на 3-4 строчки, чем проморгать что-то не то)
А насчёт проблем с восприятием разреженного кода — это скорей вкусовщина и дело привычки. С однострочными if беда в том, что условие всегда разной длины, и в любом случае приходится искать глазами где оно кончается. А двустрочные if без фигурных скобок просто опасны — очень легко в состоянии потока добавить строчку-другую, забыв про необходимость блока.
Мне приходилось одновременно вести проекты на Java/Go/C++ с разными кодстайлами, и всё-таки вариант, где после if чётко выделялся выделяется блок переносами строк, в итоге воспринимался гораздо лучше. Всё потому, что всё всегда на одних и тех же позициях относительно начала строки, нет необходимости взглядом парсить условие, чтобы найти начало блока или выражения. В итоге такой разреженный код можно осознанно скроллить гораздо быстрее, чем более плотный. Блоки в if'ах выхватываются взглядом моментально.
То же касается разделения логически связанных участков кода пустыми строками — это разгружает мозг при чтении кода. Код более разреженный, но воспринимается гораздо лучше.
Кто не добавлял к 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/)
Я один прочитал "6. ... и мешают писать компактный ад"?
Нет смысла проверять, удалось ли выделить память. На современных компьютерах её много. А если не хватило, то и незачем дальше работать. Пусть программа упадёт. Все равно уже больше ничего сделать нельзя.
На самом деле, даже если без шуток, это нормальный совет для большинства программ. Если мы не можем больше выделить нисколько памяти, то сделать что-то осмысленное - очень сложно, а выхлопа - мало, потому что, скорее всего, нам в любом случае надо завершиться. ОК, может быть, в каких-то редких случаях, например, если мы пишем какой-то системно-важный демон или пишем embedded софт, дополнительные усилия могут быть оправданы. Но в среднестатистической программе падать на OOM - это стандартная рекомендация.
Кстати, насколько я знаю, в Rust это стандартное поведение библиотеки - немедленное завершение при OOM. То же делает аллокатор в реализации Chrome.
Но в среднестатистической программе падать на OOM - это стандартная рекомендация.
проблема только в одном - в линуксе можно аллоцировать сколько угодно памяти, только программе упадет не на ее аллокации, а когда реально выяснится, что ее нет в системе и к тому же просто придет уже системный ООМкиллер и всех порешает ((((
С malloc-то всё понятно, ежели написано что иногда она таки 0 возвращает — надо бы проверить.
Но мы же про С++ говорим, где используется new.
Дак и new либо швыряет std::bad_alloc, либо в нешвыряющей версии nullptr отдает (безусловно, есть исключения с преопределением new и/или std::set_new_handler...)
Если зайти достаточно глубоко в new, то можно там найти malloc.
Выше предлагалось падать, а не продолжать работь. Не знаю что имел в виду автор, но это лучше всего делать переопределив malloc на функцию вызывающую exit() если память кончилась. Тогда все приведенные в той статье аргументы не применимы.
ИМХО, самый разумный способ разрулить это на уровне операционной системы, «замораживая» приложения в случае невозможности аллокации памяти, и предоставляя пользователю самому решать что делать: принудительно завершить приложение, либо оставив его «замороженным» закрыть другие приложения [дабы освободить немного памяти], разморозить «замороженное» приложение, сохранить свои данные и закрыть его (чтобы перезапустить).
Плохая, негодная, нерабочая стратегия. Есть же оомкиллер в линуксе. Единственная нормальная стратегия:
брать столько памяти, сколько нужно и ни байтом больше. (С учётом пиковой нагрузки)
Если мы крашнулись по оперативе - сохраняем данные на диск без потери и даём пользователю или демону супервизору перестартовать наше приложение
Все остальное скорее приводит к ещё большим проблемам, чем изначально было
Ваша стратегия для серверов, а я имел в виду стратегию для десктопов (забыл уточнить просто).
Если мы крашнулись по оперативе — сохраняем данные на диск без потери
Это о каких данных идёт речь?
И что если для сохранения на диск (в случае когда это сохранение выполняется на уровне приложения, а не операционной системы) требуется немного дополнительной памяти, а она кончилась?
А теоретически такое может быть? Да, может. Но это что-то особенное. Про один из таких интересных случаев вы можете услышать в докладе "Не связывайтесь с поддержкой C++ программистов". Это там, где про строковый литерал на 26 мегабайт.
Написал про всё это подробнее: Какая стратегия освобождения памяти используется в C и С++ ядре PVS-Studio?
В дополнение к демону и embedded-софту это так же относится и к библиотекам: никогда на перед не угадаешь, в каком окружении они будут использоваться. Да и валиться из библиотеки в фатальное завершение программы - моветон. Лучше вернуть ошибку, пусть тот, кто библиоткеу использует, решает, как с этим жить (или не жить) дальше.
В 10 и половина приложений не выдает в журнал что схлопнулось по причине нехватки озу. А можно ведь еще своп раскорячить на много гигов, да на ссд. Как думаете, сколько виндовых приложений из 100 будет его бессовестно жрать и молча причмокивать? Сама 10ка по поводу нехватки озу давно не парится. Не будет вам ни логов информативных ни bsod с перезагрузкой.
Вопрос в том, сколько памяти вы захотели выделить. Если вам не дают гигабайт, это ведь не значит, что свободных пол-гига вашей программе не хватит, чтобы сохранить работу пользователя?
Или это паттерн программирования "обидка" -- ах, операция не удалась? ну, всё!
std::vector
, которая при необходимости увеличить буфер пытается увеличить его сразу вдвое, и если не получилось — то пытается увеличить впритык для добавляемых элементов.А вы бы как написали? При каждом добавлении делали бы реаллокацию впритык?
Полгига может быть достаточно не для выполнения той же операции, а, например, для корректного завершения программы с сохранением промежуточных результатов.
Хотя вариант с переходом на более медленный или менее точный алгоритм с меньшим потреблением памяти тоже возможен. Предварительная проверка на доступный объем памяти может не сработать, поскольку память может быть выделена конкурмрующим потоком или процессом между проверкой и собственно выделением.
Еще можно просто подождать, пока память не освободится. Случаи разные бывают.
Например, пользователь работает в графическом (текстовом, видео, аудио) редакторе, у него открыты несколько несохраненных файлов, несколько часов работы. И вот он попытался открыть большой файл... Как же он будет вас ненавидеть с походом "пусть падает"!
Имеет смысл по крайней мере правильно финализировать работу программы - освободить ту память и прочие ресурсы (файлы), которые уже были заняты. В противном случае при первой же попытке работать с такой программой в автоматическом режиме (например, при помощи скрипта на bash), например вызывать её до первого успешного исполнения (то есть до экзит-кода 0), мы легко можем получить проблемы уже в пользовательской системе.
Занимаемую процессом память, как и прочие ресурсы (файлы), освободит ОС.
Если что и нужно финализировать перед падением — так это сбросить буферы вывода.
Финализируют работу программы автоматически вызванные деструкторы. И std::terminate. В случае крайней небходимости std::terminate_handler. Ни в коем случае эта логика не должна стоять над каждым выделением памяти, особенно учитывая что оно ДОЛЖНО быть неявным в современном С++(то есть без явного new)
В итоге для 99% программ достаточно
int main() try {
...
} catch (std::bad_alloc&){
}
На самом деле, даже если без шуток, это нормальный совет для большинства программ.
А потом мы видим статьи «Почему в современном софте всё всрато»...
В embedded выделять память после инициализации явно запрещено. А ещё лучше вообще всю память выделять статически, в смысле без динамического выделения вообще.
Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа 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).
От себя лично добавлю. Так сказать из своего опыта.
Никогда не тестируйте. И не пишите тестов. Ваш код идеален, что там тестировать!
Всегда и везде выкатывайте любые изменения сразу на продакшен. Тестовые сервера - лишняя трата денег.
У всех пользователей есть десятигигабитный интернет и четырехпроцессорные ноутбуки. 21-й век на дворе все таки.
Любой заказчик рад оплачивать ваши счета, даже если вы ничего не делаете. Просто шлите больше счетов.
Всегда, при любых ситуациях, используйте как можно большее количество вложенных друг в друга объектов. Сложность - это надежно! Сложность - это солидно!
Никогда не используйте платные компоненты. Только ломанные и с как можно более "левых" иранских и пакистанских сайтов. За что этим бездельникам платить деньги, в конце концов.
Общайтесь с заказчиками с помощью онлайн переводчиков. У нас слова вылетели. Проблема с пониманием на той стороне.
Не делайте бакапы. Вы всегда сможете, за пару дней, по памяти, восстановить проект, который был в разработке больше года.
Чтение документации вредит вашему здоровью. Что, эти жалкие фигляры, там могут написать такого что вы не знаете.
В дополнение к отсутствию тестов: если приходится тестировать, то мокайте все подряд (даже соседние классы, которые вы же и написали), ведь только так вы сможете смоделировать удобное вам поведение окружения, чтобы тесты смогли проходить идеально. Пусть реальное окружение будет отличаться от моков: чем противоречивее, тем лучше, ведь только так вы сможете доказать, что ваш код идеален, а окружающая действительность должна подстраиваться под ваш код.
С третьим пунктом не согласный я. Есть ситуации в которых сравнение вещественных чисел на равенство - осмысленное и хорошо определённое действие.
Только надо быть осторожным, потому что даже безобидные вещи вроде "double y=f(x); ... if (y==f(x))" могут работать неправильно (из-за промежуточных вычислений в 80-битных регистрах, насколько я понимаю).
Но согласен, что в некоторых случаях, например, бинарном или тернарном поиске, сравнение через эпсилон только навредит.
Тот пример, что вы привели - это вариант обмена корректности программы на скорость исполнения. Выигрыш обычно не велик, баги чудовищны.
Я привык думать что "детерминированные вычисления детерминированы". Нарушение этого правила - жуткая головная боль.
Прямо сейчас я работаю с программой у которой "плавают" результаты. Команда разводит руками - "это же вещественные числа, они считают примерно". Соответственно, никто не хочет привести код в порядок. Ррр.
Ах, да, вывод: приведите компилятор в соответствие с IEEE-754, имейте представление когда вычисления с плавающей запятой точные, когда нет (из-за округления) и будет вам счастье.
Ещё надо с утра повторять десять раз: "Эпсилон - часть входных данных. Я никогда не буду вписывать фиксированный эпсилон в код моей программы.".
приведите компилятор в соответствие с IEEE-754
Этого мало. При вычислениях с плавающей точкой важен, например, порядок вычислений. Это ещё и оптимизации надо отключать.
Битва за детерминизм может стоит большого количества времени (если вообще достижим, так как сами входные данные часто неточные), и нужно очень хорошо понимать, зачем это нужно и оправданно ли это. Обычно хватает просто устойчивости алгоритма, то есть чтобы при малых изменениях входных данных были малые изменения результата.
Но всё это, конечно, зависит от задачи. Универсального совета нет.
Никогда не видел чтобы оптимизатор переупорядочивал вещественные операции. Именно по причине неассоциативности операций из-за округления. Покажите пример.
Я имел в виду `-funsafe-math-optimizations` (его включает `-ffast-math`). Но вообще, я согласен, мой комментарий не совсем корректный, так как этот параметр по умолчанию выключен (собственно, это требование IEEE754-2008, чтобы по умолчанию не было меняющих значение оптимизаций).
Компилятор от 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++-код с плавающей запятой действительно скомпилируется в ожидаемую последовательность точных вычислений.
И, к сожалению, встречал, что используют "нельзя сравнивать вещественные" как мантру, не разбираясь. И эта статья этому только способствует :)
Если числа не являются результатом вычислений, то сравнивать можно и нужно.
- Не пользуйтесь стандартной библиотекой языка. Что может быть интереснее, чем написать свои строки и списки с уникальным синтаксисом и семантикой?
- Смартпоинтеры и прочее RAII от лукавого, всеми ресурсами надо управлять вручную, это делает код простым и понятным.
- И вообще, выделение памяти — зло.
char c[256]
хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае на 1024. - Глобальные переменные очень удобны, т.к. к ним можно обращаться отовсюду.
Так неинтересно, список можно продолжать бесконечно.
Что может быть интереснее, чем написать свои строки
Тем более, даже Яндекс так делает. :)
управлять вручную, это делает код простым и понятным
Выделяешь и всё. Когда процесс завершится, система сама освободит всё.
Выделяешь и всё. Когда процесс завершится, система сама освободит всё.
В копилку вредных советов.
Потом этот код либо кто-то вызовет в цикле, либо освободит память 2 раза ;)
В копилку вредных советов.
Да, совет про то, что не нужно освобождать память, вредный, конечно же. Надеюсь, никто не подумал обратного. :)
Да, я же знаю про неудаляющий аллокатор, но олимпиады — это всё же особая ветвь программирования. :)
я бы дополнил 4е правило: используйте простую систему именования переменных! зачем выдумывать разные? просто берём: int o, O, oo, oO, o0, Oo, OO, O0.
— Что у вас за код, смотреть противно. Даже идентификаторы: a, aa, aaa, a1, a2, a3… Так сейчас никто не пишет, все используют длинные мнемонически имена. Ясно?
— Ясно!
Заглядывает через неделю. Весь код переписан, везде переменные ДлинноеМнемоническоеИмя1, ДлинноеМнемоническоеИмя2, ДлинноеМнемоническоеИмя3…
Добавляйте в код как можно больше шаблонов и наследуйтесь от них как можно чаще. Ведь лучше написать один раз вложенные десять шаблонов, чем создать десять отдельных классов. Да и код с шаблонами смотрится профессиональней. А современные компиляторы всё равно очень быстрые. А если была где то допущена ошибка, то компилятор выдаст очень ясное и очевидное сообщение о том, что не так и где проблема.
Не забывайте приправлять их макросами.
Вероятно, нужно просто правил но работать с шаблонами;)
А давайте все вместе напишем статью "100 вредных советов для С++ программиста". Я начну, а вы подхватите.
Лучше потом книгу выпустите. С примерами.
Часть из них я бы назвал "советами для олимпиадников"
Всюду используйте вложенные макросы. Так текст программы станет короче, и вы сохраните больше места на жестком диске. Заодно это развлечёт ваших коллег при отладке.
Например, так:
::SetWindowText(hWnd, _T("Text"));
Вложенный макрос.
Если в строковом литерале вам нужен символ табуляции, смело жмите кнопку tab. Оставьте \t для яйцеголовых. Не парься.
Я бы вообще два раза подумал, прежде, чем хардкодить табуляцию в литерале. Хотя бы потому, что слеш легко перепутать с бэкслешем. Это про «технические» литералы, ресурсные лучше выносить в ресурсы.
Кстати, всегда включаю Show visual spaces.
Используйте для переменных имена из одной — двух букв. Так в одну строчку, помещающуюся на экране, можно уместить более сложное выражение.
Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни».
Например, i, j, k в циклах. Как сказал Сполски, «Когда я вижу indexOfItem в for, я понимаю, что чувак написал не слишком-то много циклов в своей жизни
А в какой статье? Интересно почитать, т.к. сам стараюсь вместо i/j/k использовать itemIndex.
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
Если именно itemIndex, то это ничем не лучше i. Использовать i/j/k для индексов — это общепринятая практика, то есть, когда видишь i, то и так понимаешь, что это item index. Длинное название новой информации не даёт, а читать труднее.
Выравнивание и единый стиль - для слабаков, которые не хотят вчитываться в ваш код. Заставьте их!
Побольше кода в заголовочных файлах (а лучше - весь в одном!), ведь так гораздо удобнее, а время компиляции возрастает очень незначительно.
Злые языки говорят, что goto считается вредным, но это чушь. Этот оператор очень мощен и способен заменить почти все другие конструкции языка, старайтесь пользоваться им почаще. А для переходов между функциями используйте setjmp/longjmp
Никогда не используйте enum'ы, они все равно неявно приводятся к int; используйте int напрямую!
Старайтесь писать документацию как можно подробнее, каждый метод, каждая функция должны быть снабжены развесистым комментарием. Но никогда не давайте примеров использования в документации, это дурной тон! Вы же не считаете пользователей вашего кода дураками?
Используйте как можно больше разных систем сборки и пакетных менеджеров, покажите, что вы в курсе современных тенденций! Разумеется, версии кода в пакетах для разных менеджеров должны быть немного разными, иначе пользователи заскучают.
Вы ведь слышали, что стандартные примитивные типы в С и С++ не имеют гарантированной длины в битах? Чтобы справится с этой проблемой лучше всего завести свои псевдонимы для примитивных типов, с указанной фиксированной длиной. Ни в коем случае не пользуйтесь типами из
stdint.h
, ведь существование этого файла не гарантируется стандартом, а значит, ваш код не будет действительно переносимым!Проявите немного уважения к программистам прошлого, объявляйте все переменные вначале функций. Это - традиция!
Использовать венгерскую нотацию - тоже хорошая, проверенная временем практика; посмотрите в конце концов на WinAPI
Всегда и везде используйте enum'ы. #define для слабаков. Постарайтесь как можно чаще использовать enum'ы с разрывами. Также, для строгой типизации не используйте bool, а используйте enum MyFlagType { Yes, No } или enum MySecondFlagType { Maybe, No, Yes } Так приятно, когда человек, отсчитывая по строчке от начала разрыва узнаёт, что загадочное число 27543 в логе означает MSG_CLOSEALL.
.. хм, в принципе, я реально предпочел бы enum вместо дефайнов почти всегда. По крайней мере если функция аргументом принимает enum - сразу видно возможные варианты, а вот если просто int - то уповать на документацию или ванговать
Во-первых, enum'ы, при прочих равных, считаются лучше, чем define'ы. Чем лучше?
Во-вторых, bool, вообще-то, действительно часто советуют заменять на enum'ы. Чтобы понять, почему, предлагается взглянуть на следующий псевдокод:
CreateWindow("MyClass",
"MyTitle",
true,
false,
false,
true,
true);
…и сравнить с таким вариантом:
CreateWindow("MyClass",
"MyTitle",
WindowVisibility::Visible,
WindowState::Disabled,
WindowChild::No,
WindowType::ToolWindow,
WindowModal::Modal);
Если развить эту мысль до конца, некоторые вообще считают, что неименованный bool общего назначения в параметрах нужен весьма редко, например, для интеропа и всяких расширений, а в остальных случаях не надо лениться расшифровывать оба состояния по имени.
А ещё можно switch вместо goto использовать как в Duff's device — ставим метки case на разные части тела одного цикла.
Если goto использовать нельзя (например, из-за линтера или код-стайла), то можно попробовать цикл do ... while(true/false)
в сочетании сbreak/continue
.
Бесконечный цикл с while(true)
и break — это нормально, называется "цикл с условием посередине".
Бесконечный цикл с while(false)
— это WTF, который допустим лишь в макросах.
Бесконечный цикл с
while(true)
и break — это нормально, называется "цикл с условием посередине".
если это условие выполняется в принципе... хотя по-всякому бывает :-) мало ли это какой-то софт для контроллеров.
do {
...
if (smth) break;
...
} while(false)
Выглядит как цикл, но по-сути это goto в конец блока. Чтобы понять это, надо дочитать до конца цикла. Особенно весело, когда такие "циклы" идут вперемежку с нормальными.
if (! smth) {
...}
Пиши в хидерах код а-ля
using namespace std;
- коллеги скажут спасибо, за то что им не придется это писать в каждом .cpp!Подключай как можно больше хидеров, чтобы каждый .cpp файл раскрывался в 1mln строк - коллеги скажут спасибо за то, что у них больше времени на перекур во время пересборки!
Функция должна вернуть больше чем одно значение? Есть крутой лайфхак - записывай результаты в указатель! Для трёх значений это
int process(int arg, int* res2, int* res3)
.
int process(int arg, int* res2, int* res3)
может все-таки:
int process(int arg, int& res2, int& res3)
? по сути то же самое, но немножко более с++ нативно
Подозреваю, что коллеги предлагают написать класс TheProcessFunctionResults... Ну, и, конечно, определить для класса конструкторы, сеттеры, геттеры и, возможно, операторы. Ну а метод TheProcessFunctionResults::stringifyError(), как вишенка на торте, не оставит никаких сомнения у читателя в необходимости наличия этого класса.
Извиняюсь. Сарказм от сишника.
А что сарказм-то? Класс и правда нужен. Правда, можно обойтись std::tuple<int, int, int>
, но отдельный класс с именованными полями ещё лучше.
Аксцессоры только этому классу не нужны нахрен, полей будет достаточно.
Туплы совсем нечитабельно будут выглядеть, на мой взгляд. Против голого класса я бы не поморщился, хотя и предпочёл бы
int process(int arg, int *res1 = nullptr, int *res2 = nullptr)
в случае, если результаты не всем и не всегда нужны. Пример из qt:
QString::toFloat(bool *ok = nullptr)
Но мой подход не эталонный, у меня сишная деформация.
А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!
А что там нечитаемого-то? Сравните:
auto [x, y, z] = process(arg);
int y, z;
int x = process(arg, &y, &z);
Для меня нечитаемым выглядит второй вариант. А первый максимально близок к тому, что ожидается от функции, которая возвращает три значения.
Что же до примера из Qt — сейчас такие вещи через std::optional делать рекомендуется.
А в случае класса как же быть с фэншуйностью? Найдётся программист, который скажет: фу, богомерзкие голые поля класса, а сам класс не в отдельном модуле!
Нахер такого советчика, и всё.
В С++ варианте со ссылками же не надо писать уродские &?
int process(int arg, int &y, int &z);
...
int y, z;
int x = process(arg, y, z);
профит налицо?
И совершенно непонятно что y и z -- это возвращаемые праметры.
И параметры сразу же перестали быть опциональными.
Вариант же с tuple позволяет получить только одно значение при желании:
int y = process(arg).get<1>();
И параметры сразу же перестали быть опциональными.
к сожалению (((
А еще "бест-пректис" использовать varargs :-) https://www.gnu.org/software/libc/manual/html_node/Variadic-Functions.html Но это тащить в С++ точно не стоит
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)
С учётом сказанного, предпочёл бы выбор "паковать в класс" либо "писать по указателю".
Пишите ваши .h-файлы так, что они зависят от других заголовков, при этом не включайте их в свой заголовочный файл. Пусть тот, кто инклудит, догадается, какие заголовки нужно заранее заинклудить перед использованием вашего файла. Чем больше внешних не упомянутых зависимостей - тем лучше, ведь это так увлекательно проходить квест по компиляции проекта
Наткнулся на такую хрень в реальном проекте недавно. Пытаться раскрутить зависимости и найти нужные файлы это крайне увлекательно. (Не) рекомендую.
Другая крайность: инклудить все подряд, ведь ты заранее не знаешь, насколько развернется полет твоей мысли, поэтому в дефолтном шаблоне для .h-файлов надо заранее включить
#include <stddef.h>
#include <windef.h>
#include <Qt>
Ведь эти файлы наверняка есть на всех платформах
они зависят от других заголовков, при этом не включайте их
- Если используете какую-то библиотеку — вовсе не обязательно ее добавлять в сборочные файлы. И уж точно не нужно фиксировать ее версию. Это отсечет новичков от которых одни проблемы.
К 4 пункту помимо 2-3 буквенных обозначений названий переменных можно смело добавлять:
1) с названиями классов и методов(функций) делайте то же самое
2) можно использовать в названиях классов, функций, переменных транслит латинскими буквами. Так много русских коллег, читающих ваш код, лучше вас поймут. А на остальных плевать.
3) если вы даете названия переменным, функциям и классам на англо-бурятском, потому что английский у вас - твердый а1, не лезьте никогда в словарь, чтобы проверить как пишется слово или словосочетание, устойчивое выражение. Если среда разработки ругается на орфографию - отключите. Так вас вас поймет максимальное число и русских и иностранных разработчиков, - вы ведь только изучаете английский, тренируя память и придумывая новые бессмысленные выражения.
4) используйте в названиях функций(методов) get и set везде, где вам кажется удобным, даже если это не гэттеры и сэттеры.
Зато когда фрагмент вашего кода с Гитхаба используют для взлома Пентогана, у ЦРУ появятся "неопровержимые доказательства
Тут такое дело... Есть протокол, который определяет структуры сетевого обмена. Все поля этих структур (а их сотни) являются аббривиатурами рускоязычных терминов в одной узкоспециализированной области. Шах и мат. Только транслитерация.
Не понял: о каком протоколе речь (FTP, TCP,..)? Пожалуйста, подскажите RFC. Или м.б. это ГОСТ, копирующий международный стандарт? Как в США, Европе, Японии, Китае и т.д. работают с ру-протоколом, не зная русский?
Нет, речь идёт не о способе передачи информации (TCP/UDP и т.д.), а о составе передаваемой информации. Например, о составе передаваемых данных между автоматизированными изделиями некоторого программно-аппаратного комплекса.
Конечно, бывают исключительные случаи. Но ИМХО тут помогут комментарии, которые можно писать на любом языке. Мой пример:
soxranitinapechatatresultat
Можно записать:
SPR, // save & print result — сохранить и напечатать результат
Транслитерация не всегда однозначна. Такие буквы, как щ, ш, э, ъ, я, ч, ы часто переводят по-разному. И возникают ошибки.
Пробовал и через комментарии. Тратил очень много времени на корректный перевод технических терминов и правильное именование (это мне впоследствии помогло хорошо ориентироваться в импортной литературе, так что не зря). Проблема такого подхода в необходимости постоянно "подглядывать" в комментарии чтобы вспомнить имя какого-нибудь поля с нераспространённым или уникальным названием. В то время как русскоязычная аббривиатура постоянно наслуху. Другая проблема в отличии понятийных баз. Резюмируя, признаюсь, что конкретно в этом случае я не ругаюсь на коллег за транслитерации. Если вам будет интересно - пишите в личку - дам примеры, которые лучше пояснят глубину проблемы(?) в одной конкретной отрасли.
Зачем нужны все эти
*_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!
Никакая система сборки не знает ваш код лучше вас. Лучше напишите простой скрипт, который компилирует всё шаг за шагом. Ну да, длинно, зато надёжно.
А вы знаете, что вместо фигурных скобок использовать <% и %>? Диграфы и триграфы могут придать вашему коду визуальную свежесть и необычность, выделит его на фоне кода коллег. И при этом ничего незаконного, они же есть в стандарте.
Зачем инициализировать переменные, если там и так нули? Я вот недавно не инициализировал и там ноль был. Всё работало.
private для параноиков. Кому они нужны, эти поля класса?
Пишите код так, как будто его будет читать председатель жюри IOCCC, и он знает, где вы живёте (чтоб приехать и вручить вам приз).
Если в C++ переносы строк и отступы незначимые, то почему бы не оформить код в виде зайчика или белочки?
Если программа компилируется, значит все ОК. Это ж C++, тут типы и проверки!
Самый главный вредный совет: пишите на C++, используйте его!
Да-да, все уже давно поняли, что либо пишите на питоне, либо вообще не программируйте. Успокойся уже.
Лучше так - пишите ВСЁ на C++:
Надо автоматизировать что-то в *никсах? Собственная программа на C++ сделает это лучше всяких неведомых Баш-скриптов. А то всякие пайпы ещё придумали, изверги...
Распарсить ответ от сайта? Зачем Питон? Ведь это надо учить ещё один язык. Ещё великий Брюс Ли боялся не того, кто повторил тысячу ударов - а того, кто повторил один удар тысячу раз!
Сделать сайт? Вы будете смеяться, но с современными технологиями можно компилировать C++ в Web Assembly. Ведь в термине "веб-приложение" главное слово - это "приложение". А приложения, как известно, пишутся на C++.
Android-разработка? Что значит "вся платформа построена вокруг Java"? У нас есть NDK, у нас есть Qt. А если кому-то не нравится, как выглядит результат - может закрыть глаза.
Используйте числа в программировании. Так ваша программа будет выглядеть умнее и солиднее. Согласитесь, что такие строки смотрятся хардкорно: qw = ty / 65 — 29 * s;
i = 0x5f3759df - (i >> 1);
Оформлять стандартный код через устоявшиеся идеомы - тривиально. Разнообразьте написание кода. Пусть читающий ваш код размышляет и компилирует код у себя в голове, а не просто видит знакомые фрагменты текста.
И самый хардкор...
Используйте осмысленные имена, которые имеют другой (но почти такой, как заявленый) функционал в коде. Внесение изменений в алгоритм - это не повод переименовать все переменные.
Новый совет: не трать время на документацию и пояснения. Код перед глазами. Разберемся.
Однажды попал на туториал по С++ на Ютубе, где спикер пользовался 8-ым советом, когда пытался получить доступ к десятому элементу массива длинной десять.
Во всех старых книгах для хранения размеров массивов и для организации циклов использовались переменные типа int. Так и делай. Не стоит нарушать традиции.
Если речь о size_t или unsigned int, то многие не согласятся с этим пунктом. STL за это критиковали многие гуру C++. Eigen, например, использует именно int.
ОК, Alexandresku и Stroustrup заблуждались.
специально для минусаторов - https://www.youtube.com/watch?v=Puio5dly9N8#t=42m40s
можно начать с этого - https://github.com/ericniebler/stl2/issues/182
"If you need an extra bit, I am really reluctant to believe you" - классика :)
Также QT придерживается int-стиля.
unsigned int / int может как-бы быть недостаточно…
Смотря для чего. В моей практике MAX_INT в 99,9% случаев заведомо достаточно (если забыть о существовании некоторых платформ с разрядностью < 32 бит).
Могу привести такой контраргумент на тему достаточности. Если Вы допускаете, что у вас, предположим, длина вектора может быть size_t (то есть на 32-битных платформах не 2^31, а 2^32 элементов), то я ожидаю, что ваш код адаптирован к работе с большими массивами памяти (от 4 Гб и выше). На самом деле почти всегда это не так. Подобная адаптация - это очень серьёзный и очень частный для конкретной задачи вопрос. Чтобы это понять, попробуйте произвольный вектор в своей программе забить 2^32 элементами и расскажите что получится (желательно не vector<uint8_t>, а что-нибудь позабористей). Если программа не упадёт по нехватке памяти, то обратите внимание на производительность и проверьте загрузку винта, на который пишется SWAP. Потом выключите свапирование, т.к. полагаться на него не очень правильно.
В qt6 уже используется qsizetype (=ptrdiff_t)
Да. При этом ptrdiff_t = ssize_t. Оставляя знаковый бит, авторы не погнались за тем "чтобы было достаточно", ограничившись 2^31 на 32-битных платформах (или 2^63 на 64-битных). Это похоже не компромисс между аргументом в пользу адресной арифметики и в пользу организации знаковых циклов for.
Да нет, это полечит боль тем, кто пишет
for (int i = arr.length(); i--; i>=0)
P.S.: посылаю лучи проклятья тем, кто придумал этот хабраинлайн-редактор и ни разу не пытался использовать его в реальной работе на мобилках для написания сниппетов кода
http://advsys.net/ken/add-128.htm
Может быть используют signed int ещё и по этой причине.
Используй самый древний и самый старый стандарт С++, который сможешь найти. Пускай твои коллеги удивятся и дадут тебе медальку ачивку Археолог. Зато твой код сможет скомпилироваться на любом компиляторе - они ж должны поддерживать старые стандарты?
Либо... используй самый новый стандарт и все те возможности, которые в него вошли как draft... Покажи всем какой ты крутой джедай и как ты хорошо знаешь эти bleeding edge фичи. Не смотри на коллег - они глупцы, пускай пишут на мейнстриме, ты же крутой Нео, Йода си плюс плюса.
А можно, пожалуйста, пояснения насчет 3 и 5.
Если насчет 3 еще вроде есть мысли, то с 5 вообще не понимаю как исправлять и что в принципе не так.
5 — именованные константы + желательны комментарии, поясняющие формулы
#include <windows.h>
и using namespace std;
чтобы два раза не вставать!Не забывайте делать вместо простых функций макросы. Это работает быстрее. Тем более никогда не проходите мимо min и max. Любой проект должен начинаться с определения этих макросов.
Не у каждого человека есть система сборки, поэтому вместо одного скрипта просто помести в readme все команды для компилятора.
Тестировать на других системах? Зачем, если у тебя все работает.
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;
Да уж, это насколько же программисту надо плохо думать о разработчиках компиляторов, чтобы полагать, что x /= 2
не заменится на x >>= 1
сам)
Будете смеяться, но не заменится! Хоть с -O2, хоть -O3. Потому что не имеют права, в случае если переменная signed (если оптимизатор не может доказать, что значение на самом деле всегда неотрицательно).
-3 >> 1 /* дает -2 */
-3 /2 /* дает -1 */
Компилятор обычно не может доказать что там не могут попасться отрицательные нечетные числа, для которых эта операция дает другой результат
Перегрузите как можно больше операторов.
Перегрузите как можно больше операторов, в том числе и не арифметические.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов, чтобы все операнды и результат были разнотипные.
Перегрузите как можно больше операторов, в том числе и не арифметические, для как можно большего количества типов, чтобы все операнды и результат были разнотипные, и добавьте туда побчные эффекты.
Считайте, что C++ - надмножество Си и писать надо почти так же.
Следуйте рекомендациям с https://twitter.com/affectivecpp , например:
Так как UB используется компиляторами для оптимизаций, убедитесь, что в вашем коде как можно больше UB, чтобы программа быстрее работала.
Не используйте stdint. только int, long, long long и так далее.
Благодаря обсуждениям здесь и на других площадках я подготовил более фундаментальную подборку: "50 вредных советов для C++ программиста" :). Спасибо.
Тема продолжается и развивается. Теперь есть "60 антипаттернов для С++ программиста". Там не только больше "советов", но и есть пояснение по каждому из них.
Коллекционирую вредные советы для С++ программистов