Skip to content

Instantly share code, notes, and snippets.

@alzobnin
Created January 10, 2021 15:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alzobnin/c09430499ef0d881d3d59534984750f4 to your computer and use it in GitHub Desktop.
Save alzobnin/c09430499ef0d881d3d59534984750f4 to your computer and use it in GitHub Desktop.

C++ style guide

Оформление

  1. Отступ - 2 или 4 пробела (используйте одинаковую ширину отступов во всей программе). Символы табуляции использовать для отступов запрещено. Настройка в Visual Studio 2019: Средства → Параметры → Текстовый редактор → С/C++ → Табуляция → Галочка возле "Вставлять пробелы".
  2. Отступами выделяются: тела функций, структур/классов, вложенных блоков.
  3. Бинарные операторы отбиваются пробелами с двух сторон, после унарных пробелы не ставятся: a += b + -c.
  4. Пробелы после открывающей скобки и перед закрывающей скобкой не ставятся: f(1, (2 + 3)). Закрывающая скобка должна идти на той же строке, что и последнее выражение.
  5. Максимальная длина строки - 100 символов.
  6. Перед ; пробел не ставится. После ; в for ставится пробел.
  7. Пустые блоки записываются как {} (а не ;).
  8. Открывающая { пишется на той же строке, что и начало блока (if, while, for, объявление функции).
  9. else пишется на той же строке, что и закрывающая } от if:
    if (...) {
        ...
    } else {
        ...
    }
  10. Однострочные комментарии отделяются от кода двумя пробелами и начинаются с пробела.
  11. Пробелы в конце строки запрещены.
  12. Файл должен заканчиваться переводом строки.
  13. В range-based for двоеточие обрамляется пробелами.
  14. В начале/конце блока, после public/private/protected пустые строки не ставятся.
  15. Перед объявлением функции/структуры/класса пустая строка обязательна.
  16. Секции #include и using должны быть упорядочены по алфавиту:
    // плохо
    #include <vector>
    #include <iostream>
    // хорошо
    #include <iostream>
    #include <vector>
    // плохо
    using std::vector;
    using std::cin;
    using std::cout;
    // хорошо
    using std::cin;
    using std::cout;
    using std::vector;
  17. Имя шаблона и параметр шаблона не должны разделяться пробелом:
    vector <int> v1;  // плохо
    vector<int> v2;  // хорошо

Именование

Правила наименования одни из самых важных, т. к. они помогают сразу определить какую сущность они определяют: тип, переменная, функция, константа, без необходимости поиска определения сущности. Также хорошие имена дают подсказки о работе вашего кода без необходимости каждый раз писать поясняющий комментарий.

В первую очередь вы должны повышать удобство "читателей" вашего кода, а не "писателей", т. к. зачастую код пишется один раз и переиспользуется или модифицируется много раз разными людьми. И как правило С++ разработчикам в настоящих проектах в основном приходится читать очень много кода, из-за чего следование правилам оформления повышает эффективность всех.

Используйте имена, которые описывают цель или сущность объекта. Не экономьте много символов на имена. Намного важнее, чтобы код был очевиден новым читателям с первого прочтения. Не используйте аббревиатуры, которые известны только вам. Не испульзуйте аббревиатуры, сделанные удалением символов из слов. Как правило, аббревиатура допустима, если она общеизвестна (DFS, BFS, HTTP, TCP, JSON, XML, RPC). Однобуквенные имена могут быть использованы в коротких очевидных функциях, например из 5 строк. Примеры:

  • Имя n для размера массива
  • Имя i, j, k для счетчиков цикла
  • Имя it для итератора
  • Имя e для исключения
  • Имя T для типа шаблона
  1. Имена переменных и полей классов пишутся в нижнем регистре, слова разделяются символом подчеркивания. Приватные поля классов дополнительно имеют один символ подчеркивания в суффиксе:

    int TableName;   // плохо
    int table_name;  // хорошо
    
    std::vector<int> vec, arr, res;   // плохо
    std::vector<int> scores, indices; // хорошо
  2. Не используйте транслит в именах:

    size_t dlina_massiva;  // плохо
    size_t length;  // хорошо
  3. Для переменных-счётчиков не следует использовать имена document_number, number_document, documents_count, documents_number, count_document из-за неграмотности и неоднозначности. Для числа элементов (скажем документов) можно использовать number_documents или document_count. Для функции, долго и явно подсчитывающей это число, подойдет CountDocuments().

  4. Переменные, определенные с ключевым словом constexpr и const, и чье значение определенно на все протяжение работы программы, пишутся с большими буквами, слова разделяются символом подчеркивания.

    const int MAX_SIZE = 100'000;
    const int MODULO   = 1'000'000'000 + 7;
  5. Имена типов начинаются с большой буквы, каждое слово пишется слитно и с большой буквы: MyClass.
    Под именами типов имеются ввиду следующие сущности: классы, структуры, type alias, enum, параметры шаблонов. Пример:

    class UrlTable { ...
    class UrlTableTester { ...
    struct UrlTableProperties { ...
    
    // typedefs
    typedef hash_map<UrlTableProperties *, std::string> PropertiesMap;
    
    // using aliases
    using PropertiesMap = hash_map<UrlTableProperties *, std::string>;
    
    // enums
    enum class UrlTableError { ...
  6. По умолчанию, имена функций и методов начинаются с большой буквы, каждое слово начинается с большой буквы. Имя функции должно отражать действия или задачу, которую функция делает и решает соответственно.

    AddTableEntry()
    DeleteUrl()
    OpenFileOrDie()

    Некоторые методы могут называться, как переменные. Как правило, это методы для соответствия API стандартной библиотеки: begin()/end(), size(), empty(), insert(), push_back().

    Функциям, которые возвращают bool, лучше давать имена, начинающиеся на Is или Has.

    // плохо
    bool graph_connected() { ... }
    
    // хорошо
    bool IsConnectedGraph() { ... }
    
    // плохо
    bool element(int n) { ... }
    
    // хорошо
    bool HasElement(int n) { ... }

std::vector

  1. Если требуется создать вектор размера n, воспользуйтесь специальным конструктором.
    // плохо
    vector<int> v;
    v.resize(n);
    // хорошо
    vector<int> v(n);

Циклы

  1. Если требуется перебрать элементы коллекции, предпочитайте range-based for. Он выглядит лаконичнее и легче читается.
    // плохо
    for (size_t i = 0; i < neighbors_list[vertex].size(); ++i) {
        if (!(visited[neighbors_list[vertex][i]])) {
            dfs(neighbors_list[vertex][i], visited);
        }
    }
    // хорошо
    for (const int& neighbor_vertex : neighbors_list[vertex]) {
        if (!(visited[neighbor_vertex])) {
            dfs(neighbor_vertex, visited);
        }
    }

Функции

Функции нужны

  • для избежания дублирования кода;
  • для того, чтобы можно было их переиспользовать много раз в различных проектах.
  1. Порядок аргументов функции: сначала входные параметры (по значению либо константой ссылке), затем выходные (по указателю или ссылке).
  2. Передавайте аргументы в функции по константной ссылке везде, где это уместно:
    // плохо
    void print_vector(vector<int> v) {
        ...
    }
    // хорошо
    void print_vector(const vector<int>& v) {
        ...
    }
  3. Не создавайте функции с избыточным числом аргументов. Например, в функцию, печатающую вектор, не нужно передавать размер вектора.
    // плохо
    void print_vector(const vector<int>& v, int n) {
        for (size_t i = 0; i < n; ++i) {
            cout << v[i] << " ";
        }
    }
    // хорошо
    void print_vector(const vector<int>& v) {
        // Используйте v.size(), чтобы получить длину вектора
        for (size_t i = 0; i < v.size(); ++i) {
            cout << v[i] << " ";
        }
    }
  4. Пусть у вас есть вектор vector<int> way, хранящий путь, и требуется написать функцию, которая печатает этот путь.
    // плохо
    void print_way(const vector<int>& way) {
        for (size_t i = 0; i < way.size(); ++i) {
            cout << way[i] << " ";
        }
    }
    // хорошо
    void print_vector(const vector<int>& v) {
        for (size_t i = 0; i < v.size(); ++i) {
            cout << v[i] << " ";
        }
    }
    
    // или
    
    void print_vector(const vector<int>& v) {
        for (size_t i = 0; i < v.size(); ++i) {
            cout << v[i] << " ";
        }
    }
    
    void print_way(const vector<int>& way) {
        print_vector(way);
    }
    Плохой вариант плох тем, что теряется свойство переиспользования у функции: будет неуместно использовать print_way в другом проекте, где, скажем, нужно будет распечатать вектор оценок.

Прочее

  1. Необходимо явно подключать заголовочные файлы, в которых объявляются используемые функции, классы и т. д. Запрещено явно подключать один и тот же заголовочный файл дважды.

  2. В качестве логических операторов следует использовать &&, || и т. д. Их аналоги and, or, ... запрещены.

  3. Запрещено использовать приведение типов в стиле C - следует использовать static_cast.

  4. Конструктор от одного аргумента должен быть объявлен explicit.

  5. При объявлении виртуальной функции следует использовать один и только один из спецификаторов virtual, final, override.

  6. При объявлении переменной спецификаторы static/extern/... идут перед именем типа.

  7. Везде, где это возможно, используйте префиксный инкремент и декремент:

    // плохо
    i++;
    it--;
    // хорошо
    ++i;
    --it;
  8. Пишите код так, чтобы не было предупреждений (warnings) компилятора. Это нужно по двум причинам: - Обычно компилятор выдаёт предупреждения по делу, на те места, где скрыта потенциальная ошибка. - Если игнорировать "неважные" предупреждения, их может много накопиться и вы не заметите действительно важных. В частности, избегайте сравнений знаковых (int) и беззнаковых (size_t) переменных.

    // плохо
    for (int i = 0; i < v.size(); ++i) {
        ...
    }
    // хорошо
    for (size_t i = 0; i < v.size(); ++i) {
        ...
    }

Примеры и проверка

Как делать не надо:

#include <vector>
#include <vector>
#include <stdio.h>

int extern q;

class A {
    virtual void f(void) final override;
    A(int);
    };

int main(void){

    bool a=1 or (int)(2);

    if (1) {
        f ( 2);
    }
    else {
        }
    if (2);
  &static_cast<int>(2);
    char *c;
    c [1];
    c,a; //comment
    for (auto i:std::vector<int>());
	for (;i<1;){}
    return 0;
}

Флаги для cpplint: --filter=-,+build/include,-build/include_order,-build/include_what_you_use,+build/storage_class,+readability/alt_tokens,+readability/braces,+readability/casting,+readability/inheritance,+runtime/casting,+runtime/explicit,+whitespace/blank_line,+whitespace/braces,+whitespace/comma,+whitespace/comments,+whitespace/empty_conditional_body,+whitespace/empty_loop_body,+whitespace/end_of_line,+whitespace/ending_newline,+whitespace/forcolon,+whitespace/indent,+whitespace/line_length,+whitespace/newline,+whitespace/operators,+whitespace/parens,+whitespace/semicolon,+whitespace/tab --linelength=100.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment