Общие рекомендации
Форматирование
clang-format.
2. Отступ — 4 пробела. Настройте среду разработки так, чтобы нажатие Tab добавляло четыре пробела.
3. Открывающие и закрывающие фигурные скобки должны располагаться на отдельной строке.
оператор, его можно записать в одну строку. Ставьте пробелы вокруг фигурных скобок (кроме пробела в конце строки).
if, for, while и других перед открывающей скобкой ставится пробел (в отличие от вызовов функций).
+, -, *, /, %, …) и тернарного оператора ?:.
., ->.
При необходимости оператор можно перенести на следующую строку. В этом случае отступ перед ним увеличивают.
11. Не используйте пробел для отделения унарных операторов (--, ++, *, &, …) от аргумента.
12. Ставьте пробел после запятой, но не перед ней. То же правило распространяется на точку с запятой внутри выражения for.
13. Не используйте пробелы при написании оператора [].
14. В выражении template <...> ставьте пробел между template и <; пробелы после < и перед > не ставятся.
public, private и protected на том же уровне, что и class/struct, а остальной код выравнивайте с отступом.
namespace используется во всём файле и больше ничего значимого нет, смещение внутри namespace не требуется.
17. Если блок для if, for, while или другого выражения состоит из одного оператора, фигурные скобки необязательны. В таком случае поместите оператор на отдельную строку. Это правило также распространяется на вложенные if, for, while, …
Но если внутренний оператор содержит фигурные скобки или else, внешний блок необходимо заключить в фигурные скобки.
A const (относящийся к значению) должен записываться перед именем типа.
* и & должны быть отделены пробелами с обеих сторон.
using (кроме самых простых случаев).
Иными словами, параметры шаблона указываются только в using и не дублируются в коде.
using можно объявлять локально, например внутри функции.
struct группируйте члены и функции отдельно в пределах каждой области видимости.
30. В небольших классах и struct не обязательно отделять объявление метода от его реализации.
То же самое верно и для небольших методов в любых классах или struct.
Для шаблонных классов и struct не отделяйте объявления методов от реализации (иначе их придётся определять в той же единице трансляции).
31. Можно переносить строки по достижении 140 символов, а не 80.
32. Всегда используйте операторы префиксного инкремента/декремента, если постфиксная форма не требуется.
Комментарии
///, а многострочные — с /**. Такие комментарии считаются “документацией”.
Примечание: Для генерации документации из таких комментариев можно использовать Doxygen. Но обычно Doxygen не используют, потому что по коду удобнее перемещаться прямо в IDE.
9. В многострочных комментариях не должно быть пустых строк в начале и в конце (кроме строки, закрывающей многострочный комментарий).
10. Чтобы закомментировать код, используйте обычные комментарии, а не “документирующие”.
11. Перед коммитом удаляйте закомментированные фрагменты кода.
12. Не используйте нецензурную лексику в комментариях и коде.
13. Не используйте заглавные буквы. Не злоупотребляйте знаками препинания.
Имена
using именуются так же, как и классы.
5. Имена аргументов шаблонных типов: в простых случаях используйте T; T, U; T1, T2.
В более сложных случаях либо следуйте правилам именования классов, либо добавляйте префикс T.
N.
I.
define и глобальных констант пишутся в стиле ALL_CAPS с символами подчеркивания.
- В именах переменных аббревиатура должна записываться строчными буквами:
mysql_connection(неmySQL_connection). - В именах классов и функций сохраняйте заглавные буквы в аббревиатуре:
MySQLConnection(неMySqlConnection).
enum используйте CamelCase с заглавной буквы. Также допустим стиль ALL_CAPS. Если enum не является локальным, используйте enum class.
T_PAAMAYIM_NEKUDOTAYIM
16. Сокращения допустимы, если они общеупотребительны (то есть их значение можно легко найти в Википедии или через поисковую систему).
AST, SQL.
Не NVDH (какой-то случайный набор букв)
Усечённые слова допустимы, если сокращённый вариант широко используется.
Также можно использовать сокращение, если рядом с ним в комментариях указано полное название.
17. Имена файлов с исходным кодом C++ должны иметь расширение .cpp. Файлы заголовков должны иметь расширение .h.
Как писать код
delete) можно использовать только в библиотечном коде.
В библиотечном коде оператор delete можно использовать только в деструкторах.
В прикладном коде память должен освобождать объект, который ею владеет.
Примеры:
- Проще всего разместить объект на стеке или сделать его членом другого класса.
- Для большого количества небольших объектов используйте контейнеры.
- Для автоматического освобождения небольшого числа объектов, размещённых в куче, используйте
shared_ptr/unique_ptr.
RAII; см. выше.
3. Обработка ошибок.
Используйте исключения. В большинстве случаев достаточно просто сгенерировать исключение, а перехватывать его не требуется (благодаря RAII).
В приложениях для офлайн-обработки данных часто допустимо не перехватывать исключения.
В серверах, обрабатывающих пользовательские запросы, обычно достаточно перехватывать исключения на верхнем уровне обработчика соединения.
В функциях потоков следует перехватывать и сохранять все исключения, чтобы повторно выбросить их в главном потоке после join.
errno всегда проверяйте результат и в случае ошибки сгенерируйте исключение.
- Создайте функцию (
done()илиfinalize()), которая заранее выполнит всю работу, способную привести к исключению. Если эта функция была вызвана, позже в деструкторе исключений быть не должно. - Слишком сложные задачи (например, отправку сообщений по сети) можно вынести в отдельный метод, который пользователь класса должен будет вызвать до уничтожения объекта.
- Если в деструкторе возникает исключение, его лучше записать в лог, чем скрывать (если доступен логгер).
- В простых приложениях допустимо полагаться на
std::terminate(для случаев сnoexceptпо умолчанию в C++11) для обработки исключений.
- Старайтесь добиться максимально возможной производительности на одном ядре CPU. Затем при необходимости распараллельте код.
- Используйте пул потоков для обработки запросов. Пока у нас не было задач, требующих переключения контекста в пространстве пользователя.
joinAll).
Если синхронизация нужна, в большинстве случаев достаточно использовать mutex с lock_guard.
В остальных случаях используйте системные примитивы синхронизации. Не используйте активное ожидание.
Атомарные операции следует использовать только в самых простых случаях.
Не пытайтесь реализовывать lock-free-структуры данных, если это не ваша основная область экспертизы.
9. Указатели и ссылки.
В большинстве случаев предпочитайте ссылки.
10. const.
Используйте константные ссылки, указатели на константы, const_iterator и const-методы.
Считайте const вариантом по умолчанию и используйте не-const только при необходимости.
При передаче переменных по значению использовать const обычно не имеет смысла.
11. unsigned.
Используйте unsigned, если это необходимо.
12. Числовые типы.
Используйте типы UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32 и Int64, а также size_t, ssize_t и ptrdiff_t.
Не используйте для чисел следующие типы: signed/unsigned long, long long, short, signed/unsigned char, char.
13. Передача аргументов.
Передавайте сложные значения по значению, если затем они будут перемещены, и используйте std::move; передавайте по ссылке, если хотите обновлять значение в цикле.
Если функция принимает владение объектом, созданным в куче, используйте для аргумента тип shared_ptr или unique_ptr.
14. Возвращаемые значения.
В большинстве случаев просто используйте return. Не пишите return std::move(res).
Если функция выделяет объект в куче и возвращает его, используйте shared_ptr или unique_ptr.
В редких случаях (например, при обновлении значения в цикле) может потребоваться возвращать значение через аргумент. В этом случае аргумент должен быть ссылкой.
namespace.
Нет необходимости использовать отдельный namespace для прикладного кода.
Маленьким библиотекам это тоже не нужно.
Для библиотек среднего и большого размера помещайте всё в namespace.
В файле библиотеки .h можно использовать namespace detail, чтобы скрыть подробности реализации, не нужные прикладному коду.
В файле .cpp можно использовать static или анонимный namespace, чтобы скрыть символы.
Кроме того, namespace можно использовать для enum, чтобы соответствующие имена не попадали во внешнее namespace (но лучше использовать enum class).
16. Отложенная инициализация.
Если для инициализации требуются аргументы, то обычно не следует писать конструктор по умолчанию.
Если позже потребуется отложить инициализацию, можно добавить конструктор по умолчанию, который будет создавать некорректный объект. Или, если объектов немного, можно использовать shared_ptr/unique_ptr.
std::string и char *. Не используйте std::wstring и wchar_t.
19. Логирование.
См. примеры в коде.
Перед commit удаляйте все бессмысленные и отладочные записи в журнале, а также любые другие виды отладочного вывода.
Следует избегать логирования в циклах, даже на уровне Trace.
Журнал должен оставаться читаемым при любом уровне логирования.
Логирование по большей части следует использовать только в прикладном коде.
Сообщения лога должны быть написаны на английском языке.
Желательно, чтобы журнал был понятен системному администратору.
Не используйте нецензурную лексику в журнале.
Используйте в журнале кодировку UTF-8. В редких случаях в журнале можно использовать символы не из ASCII.
20. Ввод-вывод.
Не используйте iostreams во внутренних циклах, критичных для производительности приложения (и никогда не используйте stringstream).
Вместо этого используйте библиотеку DB/IO.
21. Дата и время.
См. библиотеку DateLUT.
22. include.
Всегда используйте #pragma once вместо include guards.
23. using.
using namespace не используется. Можно использовать using для чего-то конкретного, но делайте это локально — внутри класса или функции.
24. Не используйте для функций trailing return type, если в этом нет необходимости.
virtual, а в производных классах вместо virtual используйте override.
Неиспользуемые возможности C++
Платформа
clang. На момент написания (март 2025 года) код компилируется с помощью clang версии >= 19.
Используется стандартная библиотека (libc++).
4. ОС: Ubuntu Linux, не старее Precise.
5. Код пишется для архитектуры CPU x86_64.
Набор инструкций CPU — минимально поддерживаемый среди наших серверов. В настоящее время это SSE 4.2.
6. Используйте флаги компиляции -Wall -Wextra -Werror -Weverything с несколькими исключениями.
7. Используйте статическую компоновку со всеми библиотеками, кроме тех, которые трудно подключить статически (см. вывод команды ldd).
8. Код разрабатывается и отлаживается в release-конфигурации.
Инструменты
gdb, valgrind (memcheck), strace, -fsanitize=... или tcmalloc_minimal_debug.
3. Для профилирования используйте Linux Perf, valgrind (callgrind) или strace -cf.
4. Исходники хранятся в Git.
5. Для сборки используется CMake.
6. Программы выпускаются в пакетах deb.
7. Коммиты в master не должны ломать сборку.
При этом работоспособными считаются только отдельные ревизии.
8. Делайте коммиты как можно чаще, даже если код готов лишь частично.
Используйте для этого ветки.
Если ваш код в ветке master пока не собирается, исключите его из сборки перед push. Вам нужно будет завершить его или удалить в течение нескольких дней.
9. Для нетривиальных изменений используйте ветки и публикуйте их на сервере.
10. Неиспользуемый код удаляется из репозитория.
Библиотеки
boost и Poco.
2. Запрещено использовать библиотеки из пакетов операционной системы. Также запрещено использовать предустановленные библиотеки. Все библиотеки должны поставляться в виде исходного кода в каталоге contrib и собираться вместе с ClickHouse. Подробности см. в рекомендациях по добавлению новых сторонних библиотек.
3. Предпочтение всегда отдается библиотекам, которые уже используются.
Общие рекомендации
using вместо классов или структур.
5. По возможности не пишите конструкторы копирования, операторы присваивания, деструкторы (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), конструкторы перемещения или операторы присваивания перемещением. Иными словами, функции, сгенерированные компилятором, должны работать корректно. Можно использовать default.
6. Упрощать код полезно. По возможности уменьшайте его объем.
Дополнительные рекомендации
std:: для типов из stddef.h
не рекомендуется. Иными словами, рекомендуем писать size_t вместо std::size_t, потому что так короче.
Добавлять std:: допустимо.
2. Явно указывать std:: для функций из стандартной библиотеки C
не рекомендуется. Иными словами, пишите memcpy вместо std::memcpy.
Причина в том, что существуют похожие нестандартные функции, например memmem. Мы действительно иногда используем такие функции. В namespace std их нет.
Если везде писать std::memcpy вместо memcpy, то memmem без std:: будет выглядеть странно.
Тем не менее, при желании вы можете использовать std::.
3. Использование функций из C, когда такие же доступны в стандартной библиотеке C++.
Это допустимо, если так эффективнее.
Например, используйте memcpy вместо std::copy для копирования больших фрагментов памяти.
4. Многострочные аргументы функции.
Допустим любой из следующих стилей переноса: