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

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.

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. Имена defines и глобальных констант используют 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. Многопоточность.

В офлайн-программах обработки данных:

  • Старайтесь добиваться максимальной производительности на одном ЦПУ. Затем вы можете параллелизировать ваш код, если это необходимо.

В серверных приложениях:

  • Используйте пул потоков для обработки запросов. На данный момент у нас не было задач, требующих контекстного переключения в пользовательском пространстве.

Fork не используется для параллелизации.

8. Синхронизация потоков.

Часто возможно сделать так, чтобы разные потоки использовали разные ячейки памяти (ещё лучше: разные линии кэша), и не использовать никакую синхронизацию потоков (за исключением joinAll).

Если синхронизация необходима, в большинстве случаев достаточно использовать мьютекс под lock_guard.

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

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

Не пытайтесь реализовать структуры данных без блокировок, если это не ваша основная область экспертизы.

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

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. Коммиты в мастер не должны ломать сборку.

Хотя только выбранные ревизии считаются рабочими.

8. Делайте коммиты как можно чаще, даже если код только частично готов.

Используйте для этого ветки.

Если ваш код в ветке master ещё не соби