C++ Рекомендации по стилю
Общие рекомендации
Следующие рекомендации не являются обязательными. Если вы редактируете код, имеет смысл следовать форматированию существующего кода. Стиль кода необходим для согласованности. Согласованность облегчает чтение кода, а также упрощает поиск кода. Многие из правил не имеют логических обоснований; они продиктованы установленными практиками.
Форматирование
1. Большая часть форматирования выполняется автоматически с помощью clang-format
.
2. Ширина отступа составляет 4 пробела. Настройте вашу среду разработки так, чтобы табуляция добавляла четыре пробела.
3. Открывающая и закрывающая фигурные скобки должны быть на отдельной строке.
4. Если весь код функции состоит из одного оператора
, его можно разместить в одной строке. Разместите пробелы вокруг фигурных скобок (помимо пробела в конце строки).
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
или другого выражения состоит из одного оператора
, фигурные скобки необязательны. Разместите оператор
на отдельной строке. Это правило также актуально для вложенных if
, for
, while
...
Но если внутренний оператор
содержит фигурные скобки или else
, внешний блок должен быть записан в фигурных скобках.
18. В конце строк не должно быть пробелов.
19. Исходные файлы кодируются в UTF-8.
20. Не-ASCII символы могут использоваться в строковых литералах.
21. Не пишите несколько выражений в одной строке.
22. Группируйте секции кода внутри функций и разделяйте их не более чем одной пустой строкой.
23. Разделяйте функции, классы и так далее одной или двумя пустыми строками.
24. A const
(относящийся к значению) должен быть написан перед именем типа.
25. При объявлении указателя или ссылки символы *
и &
должны быть разделены пробелами с обеих сторон.
26. При использовании типов шаблонов используйте их с ключевым словом using
(за исключением самых простых случаев).
Иными словами, параметры шаблона указываются только в using
и не повторяются в коде.
using
можно объявить локально, например, внутри функции.
27. Не объявляйте несколько переменных разных типов в одном операторе.
28. Не используйте приведение типов в стиле C.
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
и глобальных констант пишутся ЗАГЛАВНЫМИ БУКВАМИ с подчеркиваниями.
10. Имена файлов должны использовать тот же стиль, что и их содержимое.
Если файл содержит один класс, назовите файл так же, как класс (CamelCase).
Если файл содержит одну функцию, назовите файл так же, как функция (camelCase).
11. Если имя содержит аббревиатуру, тогда:
- Для имен переменных аббревиатура должна использовать строчные буквы
mysql_connection
(неmySQL_connection
). - Для имен классов и функций сохраняйте заглавные буквы в аббревиатуре
MySQLConnection
(неMySqlConnection
).
12. Аргументы конструктора, которые используются только для инициализации членов класса, должны называться так же, как члены класса, но с подчеркиванием в конце.
Суффикс подчеркивания может быть опущен, если аргумент не используется в теле конструктора.
13. Нет различия между именами локальных переменных и членами класса (префиксы не требуются).
14. Для констант в enum
используйте CamelCase с заглавной буквой. ЗАГЛАВНЫЕ БУКВЫ также допустимы. Если enum
не локальный, используйте enum class
.
15. Все имена должны быть на английском. Транслитерация ивритских слов не допускается.
not T_PAAMAYIM_NEKUDOTAYIM
16. Аббревиатуры допустимы, если они хорошо известны (когда вы легко можете найти значение аббревиатуры на Википедии или в поисковой системе).
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. Затем вы можете распараллелить свой код, если это необходимо.
В серверных приложениях:
- Используйте пул потоков для обработки запросов. На данный момент у нас не было задач, которые требовали бы переключения контекста пользовательского пространства.
Fork не используется для параллелизации.
8. Синхронизация потоков.
Часто можно сделать так, чтобы разные потоки использовали разные ячейки памяти (что еще лучше: разные кэш-линии) и не использовали никакую синхронизацию потоков (кроме joinAll
).
Если синхронизация необходима, в большинстве случаев достаточно использовать мьютекс под lock_guard
.
В других случаях используйте системные примитивы синхронизации. Не используйте busy wait.
Атомарные операции должны использоваться только в самых простых случаях.
Не пытайтесь реализовать структуры данных без блокировок, если это не ваша основная область экспертизы.
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
.
В редких случаях (обновление значения в цикле) вам может понадобиться вернуть значение через аргумент. В этом случае аргумент должен быть ссылкой.
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 в журнале. В редких случаях вы можете использовать не-ASCII символы в журнале.
20. Ввод-вывод.
Не используйте iostreams
в внутренних циклах, которые критичны для производительности приложения (и никогда не используйте stringstream
).
Вместо этого используйте библиотеку DB/IO
.
21. Дата и время.
Смотрите библиотеку DateLUT
.
22. include.
Всегда используйте #pragma once
вместо охранников включения.
23. using.
using namespace
не используется. Вы можете использовать using
с чем-то конкретным. Но делайте это локально внутри класса или функции.
24. Не используйте trailing return type
для функций, если это не необходимо.
25. Объявление и инициализация переменных.
26. Для виртуальных функций пишите virtual
в базовом классе, но вместо virtual
пишите override
в дочерних классах.
Неиспользуемые возможности C++
1. Виртуальное наследование не используется.
2. Конструкции, которые имеют удобный синтаксический сахар в современном C++, например:
Платформа
1. Мы пишем код для конкретной платформы.
Но при прочих равных предпочтение отдается кроссплатформенному или портируемому коду.
2. Язык: C++20 (см. список доступных возможностей C++20).
3. Компилятор: clang
. На момент написания (март 2025 года) код компилируется с использованием clang версии >= 19.
Используется стандартная библиотека (libc++
).
4. ОС: Linux Ubuntu, не старше Precise.
5. Код написан для архитектуры процессора x86_64.
Набор инструкций процессора является минимально поддерживаемым набором среди наших серверов. В настоящее время это 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. Коммиты в мастер не должны ломать сборку.
Хотя только выбранные ревизии считаются рабочими.
8. Делайте коммиты как можно чаще, даже если код готов лишь частично.
Используйте ветки для этой цели.
Если ваш код в ветке master
еще не компилируется, исключите его из сборки перед push
. Вам нужно будет завершить его или удалить в течение нескольких дней.
9. Для нетривиальных изменений используйте ветки и публикуйте их на сервере.
10. Неиспользуемый код удаляется из репозитория.
Библиотеки
1. Используется стандартная библиотека C++20 (экспериментальные расширения допускаются), а также фреймворки boost
и Poco
.
2. Не разрешается использовать библиотеки из пакетов ОС. Также запрещается использовать предустановленные библиотеки. Все библиотеки должны быть размещены в виде исходного кода в директории contrib
и собраны вместе с ClickHouse. См. Руководство по добавлению новых сторонних библиотек для подробностей.
3. Всегда отдается предпочтение библиотекам, которые уже используются.
Общие рекомендации
1. Пишите как можно меньше кода.
2. Попробуйте самое простое решение.
3. Не пишите код, пока не знаете, как он будет работать и как будет функционировать внутренний цикл.
4. В самых простых случаях используйте using
вместо классов или структур.
5. Если возможно, не пишите конструкторы копирования, операторы присваивания, деструкторы (кроме виртуального, если в классе есть хотя бы одна виртуальная функция), конструкторы перемещения или операторы перемещения. Иными словами, функции, сгенерированные компилятором, должны работать корректно. Вы можете использовать default
.
6. Ободряется упрощение кода. Сокращайте размер вашего кода, где это возможно.
Дополнительные рекомендации
1. Явно указывать 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. Многострочные аргументы функций.
Любой из следующих стилей обертывания допустим: