Перейти к основному содержимому
Перейти к основному содержимому

Руководство по стилю C++

Общие рекомендации

Следующие рекомендации являются рекомендациями, а не требованиями. Если вы редактируете код, имеет смысл следовать форматированию существующего кода. Стиль кода необходим для обеспечения согласованности. Согласованность облегчает чтение кода, а также упрощает поиск в коде. Многие из правил не имеют логических оснований; они диктуются устоявшимися практиками.

Форматирование

1. Большинство форматирования выполняется автоматически с помощью clang-format.

2. Отступы составляют 4 пробела. Настройте свою среду разработки так, чтобы при нажатии клавиш Tab добавлялось четыре пробела.

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. Номера символов, отличные от 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 и глобальных констант используют ALL_CAPS с подчеркиваниями.

10. Имена файлов должны использовать тот же стиль, что и их содержимое.

Если файл содержит один класс, назовите файл так же, как и класс (CamelCase).

Если файл содержит одну функцию, назовите файл так же, как и функцию (camelCase).

11. Если имя содержит аббревиатуру, тогда:

  • Для имен переменных аббревиатура должна использовать строчные буквы mysql_connection (не mySQL_connection).
  • Для имен классов и функций сохраните заглавные буквы в аббревиатуре MySQLConnection (не MySqlConnection).

12. Аргументы конструктора, которые используются только для инициализации членов класса, должны называться так же, как члены класса, только с подчеркиванием в конце.

Суффикс подчеркивания может быть опущен, если аргумент не используется в теле конструктора.

13. Нет разницы в именах локальных переменных и членов классов (префиксы не требуются).

14. Для констант в enum используйте CamelCase с заглавной буквы. Также приемлемо использование ALL_CAPS. Если enum не локальный, используйте enum class.

15. Все имена должны быть на английском языке. Транслитерация еврейских слов не допускается.

не 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.

В .h файле библиотеки вы можете использовать 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 в базовом классе, но пишите 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.

Инструкционный набор процессора является минимально поддерживаемым среди наших серверов. В настоящее время это 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. Упрощение кода приветствуется. Сократите объем вашего кода, где это возможно.