Hatred's Log Place

DON'T PANIC!

C/C++ Coding Style

Mar 29, 2016 - 10 minute read

Версия 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 - слова пишутся меленькими буквами, разделение знаком подчерка “_”

Введение

Многие элементы стиля сформировались годами, сгруппировать и оформить помогло:

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

Имена

  1. Типы данных, классы пишутся в нотации PascalCase:
    Line, NewClass
    
  2. Для интерфейсов (чисто-виртуальные классы) или абстрактных классов (которые содержат, как минимум, один, но не все) не использовать никаких префиксов и суффиксов типа 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 { ... }
    };
    
  3. Имена переменных пишутся в нотации camelCase:
    line, newValue
    
  4. Имена методов пишутся в нотации camelCase:
    line(), 
    setLine(line)
    
  5. Имена свободных функцию пишутся в нотации snake_case:
    some_free_function()
    
  6. Именованные константы (define, static const, элементы enum (но не enum class)) записываются большими буквами, разделение знаком подчерка “_”:
    MAX_COUNT
    
    1. Элементы enum class (C++11) пишутся в нотации PascalCase:
      enum class Foo
      {
          SomeItem,
          OtherValue
      };
      ...
      auto v = Foo::SomeItem;
      
  7. Области имен записываются меленькими буквами:
    io::iomanager
    
  8. Типы в шаблонах задаются одной большой буквой, либо смысловой подстановкой. Использование typename vs class vs ConceptName не регламентируется:
    // Одна буква
    template<class T> ...
    
    // Вызываемый объект (название типа определяет его смысл)
    template<typename T, typename Callable> ...
    
    // Концепт
    template<SomeTypeRestriction T, Callable C>
    
  9. В общепринятых сокращениях (HTML, DPI, etc) в нотациях PascalCase и camelCase, только первая буква большая:
    viewHtml(), htmlSource(), HttpServer
    
  10. Глобальные переменные задаются префиксом g_ (крайне рекомендуется сводить количество таких переменных к минимому):
    g_mainWindow, g_someInstance
    
  11. Приватные и защищенные переменные в классах начинаются с префикса _:
    class Foo
    {
    private:
    
        int _length;
    };
    
  12. Публичный переменные классов и структур пишутся без префиксов, как обычные переменные:
    class Foo
    {
    public:
        int length;
    };
    
  13. Переменные в прототипах функций должны иметь осмысленные названия, не использовать (по возможности) сокращений.
  14. В качестве счетчиков циклов использовать переменные i, j, k, l, m, n; для временных символьных и десятичных величин можно использовать переменные c, d.
  15. Имена - английские слова.
  16. Для переменных, описываюих количество каких-то элементов или значений, используйте суффикс Count:
    size_t itemsCount;
    
  17. FIXME (не очень хорошее правило) Для булевых (bool) методов и переменных используйте префикс is и вариации типа has, can, should и т.п.:
    isSet(), isProcessInProgress
    
  18. Для методов, призванных манипулировать значениями переменных класса:
    1. для геттеров - не использовать никакие префиксы (допускается префикс is/has/can/etc для булёвых проверок):
      value();
      line();
      
    2. для сеттеров - использовать префикс set:
      setValue(value)
      
  19. Для инициализации каких-то значений, используйте в имени метода используйте префикс initialize (допускается init):
    initializeGraphicsView(); initSpeller();
    
  20. По возможности используйте другие парные префиксы:
    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, ...
    
  21. По возможности в именовании булевых переменных используйте прямую логику:isError, но isNoError
  22. Перечесления должны содержать общую для типа часть:
    enum Color
    {
        COLOR_RED,
        COLOR_GREEN
        COLOR_BLUE
    };
    
    1. не распространяется на C++11 enum class - по именованию - выше.

Файлы исходных текстов

  1. Имена исходников меленькими буквами

  2. Каждый класс в своей паре cpp/h, имя файла соответствует имени класса: mycustomevent.h, mycustomevent.cpp

  3. Не использовать реализации метода в заголовочном файле (кроме коротких, которые рекомендуется заинлайнить)

  4. Использовать максимальную длину строки 120 символов

  5. Базовый отступ 4 пробела

  6. Не стесняться в использовании пробелов при составлении выражений, особенно касается математических формул.

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

    total = a + b + 
            c + d;
    
    function(param1,
             param2);
    
  8. Заголовочные файлы должны включать include-защиту. Предпочитать:

    #pragma once
    

    классическому подходу:

    #ifndef HEADER_FILE_NAME_H
    #define HEADER_FILE_NAME_H
    
    ...
    #endif /* HEADER_FILE_NAME_H */
    

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

  9. Директивы #include группировать по смыслу, в частности более низкоуровневые располагать выше остальных:

    #include <iostream>
    #include <iomanip>
    
    #include <QUdpServer>
    #include <QDialog>
    
    #include "projectheader1.h"
    #include "projectheader2.h"	
    
    1. Исключение: директивы STL размещать выше директив STDC: они являются “родными” для языка.
    2. В C++ коде предпочитать использовать C++-врапперы инклудов, типа cerrno вместо errno.h
  10. Директивы препроцессора (кроме условных) стараться располагать в начале файла

Определения

Типы

  1. Стараться уменьшать области видимости типов данных

  2. Директивы public, protected, private располагать в порядке уменьшения их публичности

  3. Для методов и переменных класса использовать разные секции public, protected, private

  4. Конструктор и деструктор класса располагать первыми в public секции (если не предполагается их закрытие в private/protected)

  5. Использовать явное приведение типов:

    • C:

      floatValue = (float)intValue;
      
    • C++:

      floatValue = static_cast<float>(intValue);
      

Переменные

  1. Инициализируем переменные там же, где их определяем

  2. Переменные должны называться так, что бы не вызывать неоднозначности в их трактовке

  3. Как выше писал, старайтесь уходить от глобальных переменных

  4. Опять таки, не делайте публичными переменные класса, используйте для манипуляции с ними геттеры/сеттеры

  5. FIXME (спорный пункт) Опускать сравнение с нулем можно только для типа bool:

    if (linesCount != 0) // НЕЛЬЗЯ: if(linesCount)
    if (isSet) // выше: bool isSet;
    
  6. Аналогично типам данных, старайтесь уменьшать область видимости переменных

Циклы

  1. В цикле for внутри делайте инициализацию только тех переменных, которые осуществляют управление циклом:

    sum = 0;
    for (i = 0, j = 0; i < 10 && j < 2; i++) {
    
        sum += getValue(i, j);
        j    = getLatency(i, sum);
    }
    
  2. Управляющие переменные для цикла while инициализируйте перед входом в цикл, дабы не потерять.

  3. Старайтесь использовать for-each циклы C++11 для обхода коллекций.

Разное

  1. Не используйте магических чисел (кроме 0), используйте именованные константы

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

    double value = 0.0;
    double step  = 3.0e8;
    ...
    step = value1 * step / 100.0;
    
  3. При работе с вещественными числами, не опускайте не значащих нулей:

    double value = 0.5; // а не .5
    
  4. Старайтесь обходиться без goto, но допускается использовать для обработки ошибок и чистки ресурсов (паттерн goto cleanup;)

  5. Для инициализации указателя нулём используйте:

    1. C: NULL, определён в stddef.h
    2. C++11 и новее: nullptr
  6. Не используйте нескольких выражений в одной строке (разделенных ;)

  7. Следовать правилу: одна строка - одно объявление переменной:

    int x;
    int y;
    // а не: int x, y;
    
  8. В C++ программах используйте new/delete и new[]/delete[] вместо malloc

    1. Используйте сырые указатели только если в этом есть осознанная необходимость. Старайтесь использовать умные указатали, отдавая предпочтение std::unique_ptr<Type> и std::unique_ptr<Type[]>, когда нет необходимости хранить текущий размер и capacity (актуально для embedded, где std::vector требует больше места). Либо используйте контейнеры типа std::vector, std::array, std::deque.
  9. В Си программах, по возможности, используйте calloc вместо malloc, кроме случаев, когда память всё равно будет проинициализирована после вызова.

  10. При переопределении виртуальных методов используйте override.

  11. Если наследование от класса или переопределение метода не планируется, определяйте методы или классы с ключевым словом final. Использовать override для методов в этом случае не нужно.

  12. Используйте константность везде, где только можно (const-correctness).

  13. Если не предполагается, что метод или функция могут бросить исключения, объявляйте их как noexcept.

  14. Старайтесь использовать RAII где только возможно. В std::unique_ptr можно завернуть даже файловый дескриптор при использовании кастомного делитера.

  15. Рекомеендуется предпочитать использование концептов для накладывания ограничений на параметры шаблонных функций.

Оформление блоков кода

Блоки

  1. Как говорилось выше, базовый отступ - 4 пробела

  2. Если условие содержит одну строку: не используйте фигурные скобки. Но если условие содержит else и там более одной строки, то фигурные скобки используются везде.

  3. Если блок какой-то из блоков (if, else, else if) состоит из нескольких выражений (используются фигурные скобки), то остальные блоки тоже нужно обрамить в фигурные скобки, даже если они состоят из одного выражения.

  4. Фигурные скобки на той же строке для внутренних блоко (исключяния: классы, структуры, enum, union, функции, свободные блоки, case (внутри switch)):

    if (...) {
        ...
    }
    for (...) {
        ...
    }
    
  5. Декларация класса:

    class ClassFoo : public ClassBar
    {
    public:
        ...
    
    protected:
        ...
    
    private:
        ...
    }
    
  6. Реализация метода/функции:

    void function()
    {
        ...
    }
    

    или

    void
    function()
    {
        ...
    }
    
  7. Блок if-else:

    if (...) {
    
    } else {
    
    }
    ...
    if (...) {
    
    } else if (...) {
    
    }
    
    // Одиночный if
    if (...)
       ...;
    else if (...)
       ...;
    else
       ...;
    
  8. Блок цикла for:

    for (initialize; condition; update) {
        ...
    }
    
    // Empty for
    for (initialize; condition; update);
    
    // Одиночный for
    for (initialize; condition; update)
        ...;
    
    // for-each
    for (const auto &ptr : items) {
       ...
    }
    
  9. Блок циклов while и do-while:

    while (condition) {
        ...
    }
    
    // Одиночный while
    while (condition)
         ...;
    
    do {
       ...
    } while (condition);
    
  10. Блок switch:

    switch (value) {
        case ABC:
        {
            ...
            break;
        }
    
        case DEF:
        {
            ...
            // fallthrough
        }
    
        default:
        {
            ...
            break;
        }
    }
    
  11. Блок try-catch:

    try {
        ...
    } catch (const Exception& exception) {
        ...
    }
    
  12. Лямбды:

    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;
    });
    

Пробелы и другие пробельные символы

  1. Ставить пробелы:

    • вокруг знаков математических и логических операций (в том числе операций присваивания и сравнения)
    • после запятых, точки с запятой
    • рекомендуется ставить один пробел после ключевого слова языка и открывающей скобкой (с if, while, for, switch и т.п.)
  2. Разделять пустой строкой логически связанные блоки кода:

    if (isEnd)
        return;
    
    _basePoints << p;
    std::cout << "Count of points: " << _basePoints.size() << std::endl;
    
    if (_isPathAndBasePointsSame)
        _pathPoints << p;
    
  3. Выравнивать объявление переменных:

    FILE* fp;
    int   lineCount;
    char  c;
    
  4. Вообще использовать выравнивание пробелами для улучшения наглядности кода.

Исключения и дополнения

Qt

  1. Имена объектов элементов GUI (кнопки, панели, метки, текстовые окна и т.д.) задавать как обычные, не приватные переменные, и снабжать суффиксом, описывающим их принадлежность:Cmain_form, exit_button, exit_label, info_text

  2. Автоматические сгенерированные обработчики сигналов не смешивать с вручную написанным кодом и располагать в конце файла с имплементацией (точнее свой код писать выше). В заголовочном файле в сециях public signals:, prinvate signals свои обработчики объявлять выше автоматически сгенерированных.

  3. Не смешивать реализацию обработчиков сигналов и прочих функций, рекомендованная структура файла:

    // 1. Глобальный комментарий с информацией о лицензии и кратким описанием файла
    // 2. Директивы #include
    // 3. присваивание констант
    // 4. static-функции, если есть
    // 5. конструктор(ы) класса
    // 6. деструктор класса
    // 7. функции, реализующие логику класса
    // 8. созданные вручную обработчики сигналов
    // 9. созданные автоматически обработчики сигналов (обычно добавляются автоматом в конец, имеют префикс `on_`, тело, соответствующее имени объекта и суффикс `_signalName` отражающий какой сигнал обрабатывается, такие обработчики автоматически связываются moc-компилятором)
    

cioner Простые правила C++

comments powered by Disqus