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

C++ Style Guide

На данной странице описаны некоторые правила по стилю кода C++, включая рекомендации по архитектуре, форматированию, названиям сущностей и другое. Подразумеваеся, что используется стандарт C++20 или выше.

Многое из описанного ниже заимствованно из Google C++ Style Guide.

Главные принципы:

Код должен быть ориентирован на читабельность.
На чтение кода будет тратиться существенно больше времени, чем на написание.

При внесении изменений в уже реализованный код, должен использоваться тот стиль, который уже используется.

EditorConfig

charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100

Именование

ItemStyle
directory & filedash-case
variable & fieldlowerCamelCase
functionlowerCamelCase
type & enumUpperCamelCase
struct & classUpperCamelCase
class methodlowerCamelCase
namespacesnake_case (prefer single word)
macroALL_CAPS

Расширение заголовочных файлов: .hpp
Расширение файлов с реализацией: .cpp

Разделение кода

  • защита от повторного включения через include guard (#pragma once нежелателен)
  • включать только те заголовки, которые используются непосредственно в файле
  • в #include не используются UNIX псевдонимы (., ..)

Переменные

Использовать синтаксис с присваиванием. Компилятор всегда оптимизирует копирование, начиная с C++17 это закреплено в стандарте.

auto idx = 1;
auto width = 1.0f;
auto success = true;

auto point = Point{0, 0}; // copy elision
auto& pointRef = point;

При инициализации переменной можно использовать как классический синтаксис, так и унифицированный (uniform initialization).

auto v1 = std::vector<int>{255, 0};  // 255 and 0
auto v2 = std::vector<int>(255, 0); // 255 copies of 0
const auto m = std::map<int, char>({{1, 'a'}, {2, 'b'}});

Использовать ключевое слово auto при инициализации, если тип достаточно очевиден.

auto pair = std::make_pair(success, result);
SomeType someValue = x.get();

Можно использовать стандартные литералы, кастомные использовать не рекомендуется.

#include <string>

auto fn() -> void {
using namespace std::string_literals;
auto str = "example"s;
}

Функции

Используется постфиксная запись типа (trailing return type).
Исключение: тип возврата void (процедуры).

Не использовать выходные аргументы, если это возможно.

void printHello() {
std::cout << "Hello" << std::endl;
}

auto originPoint() noexcept -> const Point {
return Point{0, 0};
}

inline auto mean(float x, float y, float z) -> float {
return (x + y + z) / 3;
}

Типы

Из встроенных целочисленных типов используется только int. Если требуется тип точного размера, используются типы, определённые в стандартном заголовоке <cstdint>, например int64_t. Если это возможно, не использовать беззнаковые типы.

Не использовать typedef, вместо него есть using.

using f32 = float;
using f64 = double;

using MyMap = std::map<int, int>;

Перечисления

Используется только enum class. Если нужно убрать scope, можно использовать using enum (например в switch). Тип для перечислений указывается для всего кроме int. Инициализаторы заданы либо для всех значений, либо ни для каких.

Название перечисления в единственном числе; например, Color вместо Colors.

enum class Day {
Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday,
};

enum class Bracket : char {
CurlyOpen = '{',
CurlyClose = '}',
SquareOpen = '[',
SquareClose = ']',
};

Структуры

Используется только для типов, у которых (по задумке) все поля публичные и нет методов.

Designated initializers используются, когда поля неочевидны.

struct Point {
float x;
float y;
};
struct Rectangle {
float width;
float height;
};

auto p = Point{0, 1};
auto rect = Rectangle{.width = 1.0f, .height = 2.5f};

Классы

Порядок модификаторов доступа: public, protected, private. Модификаторы доступа пишутся без отступа, между секциями пустая строка.

В начале всегда следуют конструкторы и деструктор. Внутренние переменные оканчиваются на _.

Не использовать implicit конструкторы. По возможности не использовать множественное наследование.

class Square {
public:
Square();
~Square();

inline auto area() -> double {
return a_ * a_;
}

private:
double a_;
};

Форматирование для инициализаторов полей в конструкторе класса:

// when everything fits on one line
Class::Class() : field1(), field2() {
doSomething();
}

// if it doesn't fit, move it to a new line with indent
Class::Class(Type1 param1, Type2 param2)
: field1(param1), field2(param2) {
doSomething();
}

// when the list spans multiple lines, use alignment
Class::Class(Type1 param1, Type2 param2)
: field1(param1),
field2(param2) {
doSomething();
}

Пространства имён

Не выделяются отступами (в т.ч. вложенные), отделяется пустыми строками. Для закрывающей скобки ставится комментарий о том, какое пространство имён она закрывает.

namespace math::geometry {

class Square {...};

} // namespace math::geometry

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

if-else и switch

Не переносить else на новую строку кроме случаев с большим телом if.

if (x < 0) {
// do something
} else if (x == 0) {
// do something
} else {
// do something
}

Фигурные скобки для обработки case. Если обработка случая имеет большое тело, отделять пустой строкой.

enum class MouseEventType { MouseDown, MouseUp, MouseMove };

switch (eventType) {
using enum MouseEventType;

case MouseDown: {
state.canMove = true;
break;
}
case MouseUp: {
state.canMove = false;
break;
}
default: {}
}

Исключения

Желательно не использовать исключения. catch с новой строки когда try имеет большое тело или отлавливается несколько исключений. В случае пустого тела для catch должен быть комментарий почему это допустимо.

try {
doSomethingUnstable();
} catch {
// an explanation of why an empty body is ok
}

try {
doSomethingUnstable();
}
catch (const Exception& e) {
std::cout << "Exception" << std::endl;
}
catch (const AnotherException& e) {
std::cout << "AnotherException" << std::endl;
}

Вывод ошибок в консоль: с большой буквы, без точки на конце.

Комментарии

Для doc-комментариев: /** ... */, @-теги.
Для комментариев по реализации: // ....
Можно использовать специальные ключевые слова TODO и FIXME.

/**
* Check if is possible to construct a triangle from three points.
* Time complexity: O(1)
*/
auto isTriangle(Point& a, Point& b, Point& c) -> bool {
// checking by area
return (a.x - b.x) * (a.y - c.y) - (a.x - c.x) * (a.y - b.y) != 0;
}

Препроцессор

Не использовать препроцессор, если это возможно.

Решетка, с которой начинается директива препроцессора, всегда должна находиться в начале строки. Даже если директивы препроцессора находятся внутри тела кода с отступом, они должны начинаться с начала строки.

if (condition) {
someAction();
#if DEBUG_LOG
logger.info("...");
#endif
}

Другие источники: