C++ стиль кодирования
Общие рекомендации
Следующие рекомендации не являются обязательными. Если вы редактируете код, разумно следовать форматированию существующего кода. Стиль кода нужен для согласованности. Согласованность облегчает чтение кода, а также облегчает поиск по коду. Многие правила не имеют логических причин; они продиктованы установленными практиками.
Форматирование
1. Большая часть форматирования выполняется автоматически с помощью clang-format
.
2. Отступ — 4 пробела. Настройте свою среду разработки так, чтобы при нажатии клавиши таб добавлялось четыре пробела.
3. Открывающая и закрывающая фигурные скобки должны быть на отдельной строке.
4. Если тело функции состоит из одного statement
, его можно разместить в одной строке. Добавляйте пробелы вокруг фигурных скобок (кроме пробела в конце строки).
5. Для функций. Не добавляйте пробелы вокруг скобок.
6. В выражениях if
, for
, while
и других добавляется пробел перед открывающей скобкой (в отличие от вызовов функций).
7. Добавляйте пробелы вокруг бинарных операторов (+
, -
, *
, /
, %
, ...) и тернарного оператора ?:
.
8. Если происходит перенос строки, разместите оператор на новой строке и увеличьте отступ перед ним.
9. Вы можете использовать пробелы для выравнивания внутри строки, если это необходимо.
10. Не используйте пробелы вокруг операторов .
, ->
.
Если необходимо, оператор можно перенести на следующую строку. В этом случае отступ перед ним увеличивается.
11. Не используйте пробел для отделения унарных операторов (--
, ++
, *
, &
, ...) от аргумента.
12. Ставьте пробел после запятой, но не перед ней. То же правило относится к точке с запятой внутри выражения for
.
13. Не используйте пробелы для отделения оператора []
.
14. В выражении template <...>
используйте пробел между template
и <
; пробелов после <
или перед >
быть не должно.
15. В классах и структурах пишите public
, private
и protected
на одном уровне с class/struct
, остальные строки кода отступайте.
16. Если одно и то же namespace
используется для всего файла, и ничего другого значительного нет, отступ внутри namespace
не требуется.
17. Если блок для if
, for
, while
или другого выражения состоит из одного statement
, фигурные скобки являются необязательными. Вместо этого разместите statement
на отдельной строке. Это правило также применимо к вложенным if
, for
, while
и т.д.
Но если внутренний statement
содержит фигурные скобки или else
, внешний блок должен быть записан в фигурных скобках.
18. В конце строк не должно быть пробелов.
19. Исходные файлы кодируются в UTF-8.
20. Ненаблюдаемые символы могут использоваться в строковых литералах.
21. Не пишите несколько выражений в одной строке.
22. Группируйте разделы кода внутри функций и разъединяйте их не более чем одной пустой строкой.
23. Разъединяйте функции, классы и т.д. одной или двумя пустыми строками.
24. A const
(относится к значению) должно быть записано перед именем типа.
25. При объявлении указателя или ссылки символы *
и &
должны отделяться пробелами с обеих сторон.
26. При использовании шаблонных типов используйте для их создания ключевое слово using
(за исключением простейших случаев).
Другими словами, параметры шаблона указываются только в using
и не повторяются в коде.
using
можно объявить локально, например, внутри функции.
27. Не объявляйте несколько переменных разных типов в одном выражении.
28. Не используйте C-style приведения.
29. В классах и структурах группируйте члены и функции отдельно внутри каждого видимого диапазона.
30. Для маленьких классов и структур нет необходимости отделять декларацию метода от реализации.
То же самое относится к маленьким методам в любых классах или структурах.
Для шаблонных классов и структур не разделяйте объявления методов от реализации (иначе их нужно будет определять в том же единичном переводе).
31. Вы можете переносить строки на 140 символов, вместо 80.
32. Всегда используйте операторы префиксного инкремента/декремента, если постфикс не требуется.
Комментарии
1. Обязательно добавляйте комментарии ко всем нетривиальным частям кода.
Это очень важно. Написание комментария может помочь вам понять, что код не нужен или что он реализован неправильно.
2. Комментарии могут быть настолько подробными, насколько это необходимо.
3. Размещайте комментарии перед кодом, который они описывают. В редких случаях комментарии могут следовать за кодом, на той же строке.
4. Комментарии должны быть написаны только на английском языке.
5. Если вы пишете библиотеку, включите подробные комментарии, объясняющие ее в основном заголовочном файле.
6. Не добавляйте комментарии, которые не предоставляют дополнительной информации. В частности, не оставляйте пустые комментарии, как этот:
Пример взят из ресурса http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/.
7. Не пишите мусорные комментарии (автор, дата создания и т.д.) в начале каждого файла.
8. Однострочные комментарии начинаются с трех косых черт: ///
, а многострочные комментарии начинаются с /**
. Эти комментарии считаются "документацией".
Примечание: Вы можете использовать Doxygen для генерации документации из этих комментариев. Но Doxygen обычно не используется, так как в IDE удобнее навигировать по коду.
9. Многострочные комментарии не должны содержать пустые строки в начале и конце (за исключением строки, закрывающей многострочный комментарий).
10. Для комментариев к закомментированному коду используйте обычные комментарии, а не "документирующие" комментарии.
11. Удалите закомментированные части кода перед коммитом.
12. Не используйте ненормативную лексику в комментариях или коде.
13. Не используйте заглавные буквы. Не используйте чрезмерную пунктуацию.
14. Не используйте комментарии, чтобы обозначить разделители.
15. Не начинайте обсуждения в комментариях.
16. Нет необходимости писать комментарий в конце блока, описывающий, о чем он был.
Имена
1. Используйте строчные буквы с подчеркиваниями в именах переменных и членов классов.
2. Для имен функций (методов) используйте camelCase, начинающийся со строчной буквы.
3. Для имен классов (структур) используйте CamelCase, начинающийся с заглавной буквы. Суффиксы, кроме I, не используются для интерфейсов.
4. using
именуются так же, как классы.
5. Имена шаблонных аргументов типов: в простых случаях используйте T
; T
, U
; T1
, T2
.
В более сложных случаях следуйте правилам имен классов или добавьте префикс T
.
6. Имена шаблонных аргументов констант: следуйте правилам имен переменных или используйте N
в простых случаях.
7. Для абстрактных классов (интерфейсов) можно добавлять префикс I
.
8. Если вы используете переменную локально, можете использовать короткое имя.
Во всех остальных случаях используйте имя, описывающее значение.
9. Имена define
и глобальных констант должны использовать ALL_CAPS с подчеркиваниями.
10. Имена файлов должны использовать тот же стиль, что и их содержимое.
Если файл содержит единственный класс, назовите файл так же, как класс (CamelCase).
Если файл содержит единственную функцию, назовите файл так же, как функция (camelCase).
11. Если имя содержит аббревиатуру, то:
- Для имен переменных аббревиатура должна использовать строчные буквы
mysql_connection
(а неmySQL_connection
). - Для имен классов и функций следует сохранять заглавные буквы в аббревиатуре
MySQLConnection
(а неMySqlConnection
).
12. Аргументы конструктора, которые используются только для инициализации членов класса, должны именоваться так же, как члены класса, но с подчеркиванием в конце.
Суффикс подчеркивания может быть опущен, если аргумент не используется в теле конструктора.
13. Нет различий в названиях локальных переменных и членов класса (префиксы не требуются).
14. Для констант в enum
используйте CamelCase с заглавной буквой. Также приемлемо использование ALL_CAPS. Если enum
не локальный, используйте enum class
.
15. Все имена должны быть на английском языке. Транслитерация ивритских слов не допускается.
not T_PAAMAYIM_NEKUDOTAYIM
16. Аббревиатуры допустимы, если они хорошо известны (когда вы можете легко найти значение аббревиатуры в Wikipedia или в поисковой системе).
AST
, SQL
.
Не NVDH
(некоторые случайные буквы)
Неполные слова допустимы, если сокращенная версия часто используется.
Вы также можете использовать аббревиатуру, если полное имя указано рядом с ней в комментариях.
17. Имена файлов с кодом C++ должны иметь расширение .cpp
. Заголовочные файлы должны иметь расширение .h
.
Как писать код
1. Управление памятью.
Ручное освобождение памяти (delete
) может использоваться только в библиотечном коде.
В библиотечном коде оператор delete
может использоваться только в деструкторах.
В коде приложения память должна освобождаться объектом, которому она принадлежит.
Примеры:
- Самый простой способ — поместить объект в стек или сделать его членом другого класса.
- Для большого количества маленьких объектов используйте контейнеры.
- Для автоматического освобождения небольшого количества объектов, которые находятся в куче, используйте
shared_ptr/unique_ptr
.
2. Управление ресурсами.
Используйте RAII
и смотрите выше.
3. Обработка ошибок.
Используйте исключения. В большинстве случаев вам просто нужно выбросить исключение и не нужно его ловить (из-за RAII
).
В приложениях для офлайн-обработки данных часто допустимо не ловить исключения.
В серверах, которые обрабатывают запросы пользователей, обычно достаточно ловить исключения на верхнем уровне обработчика соединений.
В потоковых функциях вы должны ловить и сохранять все исключения, чтобы повторно выбросить их в основном потоке после join
.
Никогда не скрывайте исключения, не обработав их. Никогда просто не помещайте все исключения в лог.
Если вам нужно игнорировать некоторые исключения, делайте это только для конкретных и повторно выбрасывайте остальные.
При использовании функций с кодами ответа или errno
всегда проверяйте результат и выбрасывайте исключение в случае ошибки.
Вы можете использовать assert для проверки инвариантов в коде.
4. Типы исключений.
Нет необходимости использовать сложную иерархию исключений в коде приложений. Текст исключения должен быть понятен системному администратору.
5. Выбрасывание исключений из деструкторов.
Это не рекомендуется, но допускается.
Используйте следующие варианты:
- Создайте функцию (
done()
илиfinalize()
), которая выполнит всю работу заранее, что может привести к исключению. Если эта функция была вызвана, позже в деструкторе исключений быть не должно. - Слишком сложные задачи (например, отправка сообщений по сети) можно поместить в отдельный метод, который пользователь класса должен будет вызвать перед уничтожением.
- Если в деструкторе есть исключение, лучше его зафиксировать, чем скрывать (если логгер доступен).
- В простых приложениях допустимо полагаться на
std::terminate
(для случаевnoexcept
по умолчанию в C++11) для обработки исключений.
6. Анонимные кодовые блоки.
Вы можете создать отдельный кодовый блок внутри одной функции, чтобы сделать определенные переменные локальными, так что деструкторы будут вызываться при выходе из блока.
7. Многопоточность.
В программах офлайн-обработки данных:
- Старайтесь добиться наилучшей производительности на одном ядре CPU. Вы затем можете параллелизовать свой код, если это необходимо.
В серверных приложениях:
- Используйте пул потоков для обработки запросов. На данный момент у нас не было задач, которые требовали контекстного переключения в пользовательском пространстве.
Форк не используется для параллелизации.
8. Синхронизация потоков.
Часто можно сделать так, чтобы разные потоки использовали разные ячейки памяти (еще лучше: разные кэш-строки) и не использовали никакую синхронизацию потоков (кроме joinAll
).
Если синхронизация необходима, в большинстве случаев достаточно использовать мьютекс под lock_guard
.
В других случаях используйте системные примитивы синхронизации. Не используйтеBusy wait.
Атомарные операции следует использовать только в простейших случаях.
Не пытайтесь реализовать структуры данных без блокировок, если это не является вашей основной областью экспертизы.
9. Указатели vs ссылки.
В большинстве случаев предпочтительнее использовать ссылки.
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
.
В редких случаях (обновление значения в цикле) вам может понадобиться вернуть значение через аргумент. В этом случае аргумент должен быть ссылкой.
15. namespace
.
Нет необходимости использовать отдельный namespace
для кодов приложений.
Малым библиотекам это тоже не нужно.
Для средних и крупных библиотек всё помещается в namespace
.
В заголовочном файле библиотеки вы можете использовать namespace detail
, чтобы скрыть детали реализации, которые не нужны для кода приложения.
В .cpp
файле вы можете использовать static
или анонимный namespace
, чтобы скрыть символы.
Также namespace
может использоваться для enum
, чтобы предотвратить попадание соответствующих имен во внешний namespace
(но лучше использовать enum class
).
16. Отложенная инициализация.
Если для инициализации требуются аргументы, то обычно не следует писать конструктор по умолчанию.
Если позже вам необходимо отложить инициализацию, вы можете добавить конструктор по умолчанию, который создаст недопустимый объект. Или, для небольшого количества объектов, вы можете использовать shared_ptr/unique_ptr
.
17. Виртуальные функции.
Если класс не предназначен для полиморфного использования, вам не нужно делать функции виртуальными. Это также относится к деструктору.
18. Кодировки.
Используйте UTF-8 везде. Используйте std::string
и char *
. Не используйте std::wstring
и wchar_t
.
19. Логирование.
Смотрите примеры везде в коде.
Перед коммитом удалите все бессмысленные и отладочные логи, а также любые другие виды отладочного вывода.
Логирование в циклах следует избегать, даже на уровне Trace.
Логи должны быть читаемыми на любом уровне логирования.
Логирование должно использоваться в основном в коде приложения.
Сообщения в логах должны быть написаны на английском языке.
Лог должен быть понятен системному администратору.
Не используйте ненормативную лексику в логах.
Используйте кодировку UTF-8 в логах. В редких случаях вы можете использовать ненаблюдаемые символы в логах.
20. Ввод-вывод.
Не используйте iostreams
во внутренних циклах, критичных для производительности приложения (и никогда не используйте stringstream
).
Используйте библиотеку DB/IO
вместо этого.
21. Дата и время.
Смотрите библиотеку DateLUT
.
22. include.
Всегда используйте #pragma once
вместо защитников включения.
23. using.
using namespace
не используется. Вы можете использовать using
с чем-то конкретным. Но делайте это локально внутри класса или функции.
24. Не используйте trailing return type
для функций, если это не необходимо.
25. Объявление и инициализация переменных.
26. Для виртуальных функций пишите virtual
в базовом классе, но пишите override
, вместо virtual
в производных классах.
Неиспользуемые функции C++
1. Виртуальное наследование не используется.
2. Конструкции, которые имеют удобный синтаксический сахар в современном C++, например
Платформа
1. Мы пишем код для конкретной платформы.
Но при равных других условиях предпочтителен кросс-платформенный или переносимый код.
2. Язык: C++20 (см. список доступных функций C++20).
3. Компилятор: clang
. На момент написания (март 2025) код компилируется с использованием версии clang >= 19.
Используется стандартная библиотека (libc++
).
4. ОС: Linux Ubuntu, не старше Precise.
5. Код написан для архитектуры процессора x86_64.
Набор инструкций CPU является минимально поддерживаемым набором среди наших серверов. В данный момент это SSE 4.2.
6. Используйте флаги компиляции -Wall -Wextra -Werror -Weverything
с некоторыми исключениями.
7. Используйте статическую линковку со всеми библиотеками, кроме тех, которые трудно подключить статически (см. вывод команды ldd
).
8. Код разрабатывается и отлаживается с настройками для релиза.
Инструменты
1. KDevelop — хорошая IDE.
2. Для отладки используйте 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. Неиспользуемый код удаляется из репозитория.
Библиотеки
1. Используется стандартная библиотека C++20 (экспериментальные расширения допустимы), а также фреймворки boost
и Poco
.
2. Не допускается использование библиотек из ОС пакетов. Также не допускается использование предустановленных библиотек. Все библиотеки должны быть размещены в виде исходного кода в каталоге contrib
и собраны вместе с ClickHouse. См. Руководство по добавлению новых сторонних библиотек для получения подробной информации.
3. Всегда отдается предпочтение библиотекам, которые уже используются.
Общие рекомендации
1. Пишите как можно меньше кода.
2. Попробуйте самое простое решение.
3. Не пишите код, пока не знаете, как он будет работать и каким образом будет функционировать внутренний цикл.
4. В простейших случаях используйте using
вместо классов или структур.
5. Если возможно, не пишите конструкторы копирования, операторы присваивания, деструкторы (кроме виртуального, если в классе есть хотя бы одна виртуальная функция), конструкторы перемещения или операторы присваивания перемещения. Другими словами, функции, сгенерированные компилятором, должны работать корректно. Вы можете использовать default
.
6. Упрощение кода рекомендуется. Уменьшайте размер вашего кода, где это возможно.