Инструменты пользователя

Инструменты сайта


C/C++ Coding Style

Версия 2.0-draft
29 марта 2016г.

Изменения

Версия 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]:

  • Первая оформленная версия

Нотации

Pascal - слова пишутся слитно, каждое слово с большой буквы: NewClass
camelCase - слова пишутся слитно, первое слово с маленькой буквы, остальные с большой: newClass
Underscore - слова пишутся меленькими буквами, разделение знаком подчерка «_»

Введение

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

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

Имена

  1. Типы данных, классы пишутся в нотации Pascal:
    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. Имена свободных функцию пишутся в нотации underscore:
    some_free_function()
  6. Именованные константы (define, static const, элементы enum (но не enum class)) записываются большими буквами, разделение знаком подчерка «_»:
    MAX_COUNT
    1. Элементы enum class (C++11) пишутся в нотации Pascal:
      enum class Foo
      {
        SomeItem,
        OtherValue
      };
      ...
      auto v = Foo::SomeItem;
  7. Области имен записываются меленькими буквами:
    io::iomanager
  8. Типы в шаблонах задаются одной большой буквой:
    template<class T> ...
  9. В общепринятых сокращениях (HTML, DPI, etc) в нотациях Pascal и camelCase, только первая буква большая:
    viewHtml(), htmlSource(), HttpServer
  10. Глобальные переменные задаются префиксами (крайне рекомендуется сводить количество таких переменных к минимому):
    • C: g_
    • C++: ::
      g_mainWindow, ::mainWindow
  11. Приватные и защищенные переменные в классах начинаются с префикса m_:
    class Foo
    {
    private:
        int m_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, 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. Использовать максимальную длину строки 80-100 символов
  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
  5. Для инициализации указателя нулём используйте:
    1. C: NULL, определён в stddef.h
    2. C++98/03: 0
    3. C++11 и новее: nullptr
  6. Не используйте нескольких выражений в одной строке (разделенных ;)
  7. Следовать правилу: одна строка - одно объявление переменной:
    int x;
    int y;
    // а не: int x, y;
  8. В C++ программах используйте new/delete вместо malloc
  9. В Си программах, по возможности, используйте calloc вместо malloc, кроме случаев, когда память всё равно будет проинициализирована после вызова.
  10. При переопределении виртуальных методов используйте override.
  11. Если наследование от класса или переопределение метода не планируется, определяйте методы или классы с ключевым словом final.
  12. Используйте константность везде, где только можно (const-correctness).
  13. Если не предполагается, что метод или функция могут бросить исключения, объявляйте их как noexcept (C++11) или throw() (C++98/03).
  14. Ни в коем случае не используйте явное указание возможных к выбросу исключений (при помощи throw(except, …)), это не говорит ни о чём: может быть выброшено и любое другое исключение тоже.

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

Блоки

  1. Как говорилось выше, базовый отступ - 4 пробела
  2. Если условие содержит одну строку: не используйте фигурные скобки
  3. Если блок какой-то из блоков (if, else, else if) состоит из нескольких выражений (используются фигурные скобки), то остальные блоки тоже нужно обрамить в фигурные скобки, даже если они состоят из одного выражения.
  4. Фигурные скобки с новой строки:
    if (...)
    {
        ...
    }
  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 (Exception& exception)
    {
        ...
    }

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

  1. Ставить пробелы:
    • вокруг знаков математических и логических операций (в том числе операций присваивания и сравнения)
    • после запятых, точки с запятой
    • рекомендуется ставить один пробел после ключевого слова языка и открывающей скобкой (с if, while, for, switch и т.п.)
  2. Разделять пустой строкой логически связанные блоки кода:
        if (isEnd)
            return;
     
        m_basePoints << p;
        std::cout << "Count of points: " << m_basePoints.size() << std::endl;
     
        if (m_isPathAndBasePointsSame)
            m_path_points << p;
  3. Выравнивать объявление переменных:
    FILE* fp;
    int   line_count;
    char  c;
  4. Вообще использовать выравнивание пробелами для улучшения наглядности кода.

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

Qt

  1. Имена объектов элементов GUI (кнопки, панели, метки, текстовые окна и т.д.) задавать как обычные, не приватные переменные, и снабжать суффиксом, описывающим их принадлежность:
    main_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-компилятором)