Версия 3.0
17 апреля 2024г.
Изменения
Версия 3.0 [17 апреля 2024]
- Фигурные скобки на той же строке, что и конструкция
- Убран суффикс
m_
для членов класса, используется просто_
- Название стиля именования Pascal заменён на PascalCase
- К названию стиля именования Underscore добавлен синоним snake_case, в дальнейшем будет именно он использоваться
- Оставлен единый стиль именования глобальных переменных
- допускается использовать
prev
вместо полногоprevious
- число столбцов увеличено до 120
- уточнение по goto
- уточнение по new/delete
- добавлено упоминание лямбд при форматировании блоков кода
- добавлено предпочтение
std::ranges::
алгоритмов над их аналогами вstd::
: часто нужно передавать полностью коллекцию, и это сокращает и упрощает код - убраны некоторые упоминания C++98/03
- ослаблено требование к именованию шаблонного типа
- добавлена рекомендация использовать концепты для задания ограничений на шаблоны
Версия 2.0 [29 марта 2016]:
- Изменение в именовании свободных функций (underscore)
- TBD
Версия 1.2 [13 ноября 2010]:
- Изменение в именовании приватных переменных класса
- Рекомендации по поводу стиля при использовании библиотеки Qt
- Дополнения для логических переменных: помимо префикса
is
использовать аналогичные (has
.can
& etc) - Дополнения о порядке директив include
Версия 1.1 [8 августа 2010]:
- Для функций, в которых производятся действия по инициализации допускается использовать префикс
init
(сокращенный вариантinitialize
) - Убрана жесткая рекомендация по использованию 0 вместо NULL в C++
- Добавлена рекомендация использовать calloc вместо malloc в C
- Добавлена рекомендация добавлять пробел после ключевого слова языка и открывающей скобкой
- Рекомендация не использовать одиночные блоки if, while, for и т.п.
- Добавлена рекомендация по использованию astyle с учетом стандартов перечисленных здесь.
Версия 1.0 [2 февраля 2010]:
- Первая оформленная версия
Нотации
PascalCase - слова пишутся слитно, каждое слово с большой буквы: NewClass
camelCase - слова пишутся слитно, первое слово с маленькой буквы, остальные с большой: newClass
snake_case aka Underscore - слова пишутся меленькими буквами, разделение знаком подчерка “_”
Введение
Многие элементы стиля сформировались годами, сгруппировать и оформить помогло:
Но не со всеми аспектами, приведенными там я согласен, поэтому здесь вы найдете достаточно много различий, но в целом, данное описание стиля соответствует моему видению, кроме того, те моменты, в которых у меня не было однозначности, взяты из вышеуказанных источников, некоторые аспекты просто закреплены, дабы по ходу пьессы не возникало вопросов.
Имена
- Типы данных, классы пишутся в нотации PascalCase:
Line, NewClass
- Для интерфейсов (чисто-виртуальные классы) или абстрактных классов (которые содержат, как минимум, один, но не все) не использовать никаких префиксов и суффиксов типа
I
(IFunction
),A
(AFunction
) илиAbstract
(AbstractFile
)class Function { public: virtual ~Function() = default; virtual void call() = 0; }; class LongExecutionFunction : public Function { public: virtual void call() override { ... } };
- Имена переменных пишутся в нотации camelCase:
line, newValue
- Имена методов пишутся в нотации camelCase:
line(), setLine(line)
- Имена свободных функцию пишутся в нотации snake_case:
some_free_function()
- Именованные константы (
define
,static const
, элементыenum
(но неenum class
)) записываются большими буквами, разделение знаком подчерка “_”:MAX_COUNT
- Элементы
enum class
(C++11) пишутся в нотации PascalCase:enum class Foo { SomeItem, OtherValue }; ... auto v = Foo::SomeItem;
- Элементы
- Области имен записываются меленькими буквами:
io::iomanager
- Типы в шаблонах задаются одной большой буквой, либо смысловой подстановкой. Использование
typename
vsclass
vsConceptName
не регламентируется:// Одна буква template<class T> ... // Вызываемый объект (название типа определяет его смысл) template<typename T, typename Callable> ... // Концепт template<SomeTypeRestriction T, Callable C>
- В общепринятых сокращениях (HTML, DPI, etc) в нотациях PascalCase и camelCase, только первая буква большая:
viewHtml(), htmlSource(), HttpServer
- Глобальные переменные задаются префиксом
g_
(крайне рекомендуется сводить количество таких переменных к минимому):g_mainWindow, g_someInstance
- Приватные и защищенные переменные в классах начинаются с префикса
_
:class Foo { private: int _length; };
- Публичный переменные классов и структур пишутся без префиксов, как обычные переменные:
class Foo { public: int length; };
- Переменные в прототипах функций должны иметь осмысленные названия, не использовать (по возможности) сокращений.
- В качестве счетчиков циклов использовать переменные
i
,j
,k
,l
,m
,n
; для временных символьных и десятичных величин можно использовать переменныеc
,d
. - Имена - английские слова.
- Для переменных, описываюих количество каких-то элементов или значений, используйте суффикс
Count
:size_t itemsCount;
- FIXME (не очень хорошее правило) Для булевых (
bool
) методов и переменных используйте префиксis
и вариации типаhas
,can
,should
и т.п.:isSet(), isProcessInProgress
- Для методов, призванных манипулировать значениями переменных класса:
- для геттеров - не использовать никакие префиксы (допускается префикс is/has/can/etc для булёвых проверок):
value(); line();
- для сеттеров - использовать префикс set:
setValue(value)
- для геттеров - не использовать никакие префиксы (допускается префикс is/has/can/etc для булёвых проверок):
- Для инициализации каких-то значений, используйте в имени метода используйте префикс
initialize
(допускаетсяinit
):initializeGraphicsView(); initSpeller();
- По возможности используйте другие парные префиксы:
add/remove, create/destroy, start/stop, insert/delete, increment/decrement, old/new, begin/end, first/last, up/down, min/max, next/previous (допускает prev), open/close, show/hide, suspend/resume, ...
- По возможности в именовании булевых переменных используйте прямую логику:
isError
, ноisNoError
- Перечесления должны содержать общую для типа часть:
enum Color { COLOR_RED, COLOR_GREEN COLOR_BLUE };
- не распространяется на C++11
enum class
- по именованию - выше.
- не распространяется на C++11
Файлы исходных текстов
-
Имена исходников меленькими буквами
-
Каждый класс в своей паре cpp/h, имя файла соответствует имени класса: mycustomevent.h, mycustomevent.cpp
-
Не использовать реализации метода в заголовочном файле (кроме коротких, которые рекомендуется заинлайнить)
-
Использовать максимальную длину строки 120 символов
-
Базовый отступ 4 пробела
-
Не стесняться в использовании пробелов при составлении выражений, особенно касается математических формул.
-
При разрыве строки, делать отступ под начала выражения:
total = a + b + c + d; function(param1, param2);
-
Заголовочные файлы должны включать include-защиту. Предпочитать:
#pragma once
классическому подходу:
#ifndef HEADER_FILE_NAME_H #define HEADER_FILE_NAME_H ... #endif /* HEADER_FILE_NAME_H */
что бы избежать коллизий для файлов с одинаковыми именами в разных частях проекта (например, при подключении сторонней библиотеки).
-
Директивы
#include
группировать по смыслу, в частности более низкоуровневые располагать выше остальных:#include <iostream> #include <iomanip> #include <QUdpServer> #include <QDialog> #include "projectheader1.h" #include "projectheader2.h"
- Исключение: директивы STL размещать выше директив STDC: они являются “родными” для языка.
- В C++ коде предпочитать использовать C++-врапперы инклудов, типа cerrno вместо errno.h
-
Директивы препроцессора (кроме условных) стараться располагать в начале файла
Определения
Типы
-
Стараться уменьшать области видимости типов данных
-
Директивы
public
,protected
,private
располагать в порядке уменьшения их публичности -
Для методов и переменных класса использовать разные секции
public
,protected
,private
-
Конструктор и деструктор класса располагать первыми в
public
секции (если не предполагается их закрытие вprivate
/protected
) -
Использовать явное приведение типов:
-
C:
floatValue = (float)intValue;
-
C++:
floatValue = static_cast<float>(intValue);
-
Переменные
-
Инициализируем переменные там же, где их определяем
-
Переменные должны называться так, что бы не вызывать неоднозначности в их трактовке
-
Как выше писал, старайтесь уходить от глобальных переменных
-
Опять таки, не делайте публичными переменные класса, используйте для манипуляции с ними геттеры/сеттеры
-
FIXME (спорный пункт) Опускать сравнение с нулем можно только для типа
bool
:if (linesCount != 0) // НЕЛЬЗЯ: if(linesCount) if (isSet) // выше: bool isSet;
-
Аналогично типам данных, старайтесь уменьшать область видимости переменных
Циклы
-
В цикле for внутри делайте инициализацию только тех переменных, которые осуществляют управление циклом:
sum = 0; for (i = 0, j = 0; i < 10 && j < 2; i++) { sum += getValue(i, j); j = getLatency(i, sum); }
-
Управляющие переменные для цикла
while
инициализируйте перед входом в цикл, дабы не потерять. -
Старайтесь использовать
for-each
циклы C++11 для обхода коллекций.
Разное
-
Не используйте магических чисел (кроме 0), используйте именованные константы
-
При инициализации, работе с вещественными числами, всегда используйте десятичную точку:
double value = 0.0; double step = 3.0e8; ... step = value1 * step / 100.0;
-
При работе с вещественными числами, не опускайте не значащих нулей:
double value = 0.5; // а не .5
-
Старайтесь обходиться без
goto
, но допускается использовать для обработки ошибок и чистки ресурсов (паттернgoto cleanup;
) -
Для инициализации указателя нулём используйте:
- C:
NULL
, определён в stddef.h - C++11 и новее:
nullptr
- C:
-
Не используйте нескольких выражений в одной строке (разделенных ;)
-
Следовать правилу: одна строка - одно объявление переменной:
int x; int y; // а не: int x, y;
-
В C++ программах используйте
new
/delete
иnew[]
/delete[]
вместоmalloc
- Используйте сырые указатели только если в этом есть осознанная необходимость. Старайтесь использовать умные указатали, отдавая предпочтение
std::unique_ptr<Type>
иstd::unique_ptr<Type[]>
, когда нет необходимости хранить текущий размер и capacity (актуально для embedded, гдеstd::vector
требует больше места). Либо используйте контейнеры типаstd::vector
,std::array
,std::deque
.
- Используйте сырые указатели только если в этом есть осознанная необходимость. Старайтесь использовать умные указатали, отдавая предпочтение
-
В Си программах, по возможности, используйте
calloc
вместоmalloc
, кроме случаев, когда память всё равно будет проинициализирована после вызова. -
При переопределении виртуальных методов используйте
override
. -
Если наследование от класса или переопределение метода не планируется, определяйте методы или классы с ключевым словом
final
. Использоватьoverride
для методов в этом случае не нужно. -
Используйте константность везде, где только можно (const-correctness).
-
Если не предполагается, что метод или функция могут бросить исключения, объявляйте их как
noexcept
. -
Старайтесь использовать RAII где только возможно. В
std::unique_ptr
можно завернуть даже файловый дескриптор при использовании кастомного делитера. -
Рекомеендуется предпочитать использование концептов для накладывания ограничений на параметры шаблонных функций.
Оформление блоков кода
Блоки
-
Как говорилось выше, базовый отступ - 4 пробела
-
Если условие содержит одну строку: не используйте фигурные скобки. Но если условие содержит
else
и там более одной строки, то фигурные скобки используются везде. -
Если блок какой-то из блоков (
if
,else
,else if
) состоит из нескольких выражений (используются фигурные скобки), то остальные блоки тоже нужно обрамить в фигурные скобки, даже если они состоят из одного выражения. -
Фигурные скобки на той же строке для внутренних блоко (исключяния: классы, структуры, enum, union, функции, свободные блоки, case (внутри switch)):
if (...) { ... } for (...) { ... }
-
Декларация класса:
class ClassFoo : public ClassBar { public: ... protected: ... private: ... }
-
Реализация метода/функции:
void function() { ... }
или
void function() { ... }
-
Блок
if-else
:if (...) { } else { } ... if (...) { } else if (...) { } // Одиночный if if (...) ...; else if (...) ...; else ...;
-
Блок цикла
for
:for (initialize; condition; update) { ... } // Empty for for (initialize; condition; update); // Одиночный for for (initialize; condition; update) ...; // for-each for (const auto &ptr : items) { ... }
-
Блок циклов
while
иdo-while
:while (condition) { ... } // Одиночный while while (condition) ...; do { ... } while (condition);
-
Блок
switch
:switch (value) { case ABC: { ... break; } case DEF: { ... // fallthrough } default: { ... break; } }
-
Блок
try-catch
:try { ... } catch (const Exception& exception) { ... }
-
Лямбды:
auto it = std::ranges::find_if(container, [](auto x) { return x % 2 == 0; }); auto it = std::ranges::find_if(container, [](auto x) { return x % 2 == 0; });
Пробелы и другие пробельные символы
-
Ставить пробелы:
- вокруг знаков математических и логических операций (в том числе операций присваивания и сравнения)
- после запятых, точки с запятой
- рекомендуется ставить один пробел после ключевого слова языка и открывающей скобкой (с
if, while, for, switch
и т.п.)
-
Разделять пустой строкой логически связанные блоки кода:
if (isEnd) return; _basePoints << p; std::cout << "Count of points: " << _basePoints.size() << std::endl; if (_isPathAndBasePointsSame) _pathPoints << p;
-
Выравнивать объявление переменных:
FILE* fp; int lineCount; char c;
-
Вообще использовать выравнивание пробелами для улучшения наглядности кода.
Исключения и дополнения
Qt
-
Имена объектов элементов GUI (кнопки, панели, метки, текстовые окна и т.д.) задавать как обычные, не приватные переменные, и снабжать суффиксом, описывающим их принадлежность:
Cmain_form, exit_button, exit_label, info_text
-
Автоматические сгенерированные обработчики сигналов не смешивать с вручную написанным кодом и располагать в конце файла с имплементацией (точнее свой код писать выше). В заголовочном файле в сециях
public signals:
,prinvate signals
свои обработчики объявлять выше автоматически сгенерированных. -
Не смешивать реализацию обработчиков сигналов и прочих функций, рекомендованная структура файла:
// 1. Глобальный комментарий с информацией о лицензии и кратким описанием файла // 2. Директивы #include // 3. присваивание констант // 4. static-функции, если есть // 5. конструктор(ы) класса // 6. деструктор класса // 7. функции, реализующие логику класса // 8. созданные вручную обработчики сигналов // 9. созданные автоматически обработчики сигналов (обычно добавляются автоматом в конец, имеют префикс `on_`, тело, соответствующее имени объекта и суффикс `_signalName` отражающий какой сигнал обрабатывается, такие обработчики автоматически связываются moc-компилятором)