ClickHouse/docs/ru/development/style.md
2017-10-25 13:11:54 +03:00

43 KiB
Raw Blame History

Как писать код на C++

Общее

  1. Этот текст носит рекомендательный характер.
  2. Если вы редактируете код, то имеет смысл писать так, как уже написано.
  3. Стиль нужен для единообразия. Единообразие нужно, чтобы было проще (удобнее) читать код. А также, чтобы было легче осуществлять поиск по коду.
  4. Многие правила продиктованы не какими либо разумными соображениями, а сложившейся практикой.

Форматирование

  1. Большую часть форматирования сделает автоматически clang-format.

  2. Отступы - 4 пробела. Настройте среду разработки так, чтобы таб добавлял четыре пробела.

  3. Открывающая фигурная скобка на новой, отдельной строке. (Закрывающая - тоже.)

    inline void readBoolText(bool & x, ReadBuffer & buf)
    {
        char tmp = '0';
        readChar(tmp, buf);
        x = tmp != '0';
    }
    
  4. Но если всё тело функции достаточно короткое (один statement) - при желании, его можно целиком разместить на одной строке. При этом, вокруг фигурных скобок ставятся пробелы (кроме пробела на конце строки).

    inline size_t mask() const                { return buf_size() - 1; }
    inline size_t place(HashValue x) const    { return x & mask(); }
    
  5. Для функций, пробелы вокруг скобок не ставятся.

    void reinsert(const Value & x)
    
    memcpy(&buf[place_value], &x, sizeof(x));
    
  6. При использовании выражений if, for, while, ... (в отличие от вызовов функций) перед открывающей скобкой ставится пробел.

    for (size_t i = 0; i < rows; i += storage.index_granularity)
    
  7. Вокруг бинарных операторов (+, -, *, /, %, ...), а также тернарного оператора ?: ставятся пробелы.

    UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
    UInt8 month = (s[5] - '0') * 10 + (s[6] - '0');
    UInt8 day = (s[8] - '0') * 10 + (s[9] - '0');
    
  8. Если ставится перенос строки, то оператор пишется на новой строке, и перед ним увеличивается отступ.

    if (elapsed_ns)
        message << " ("
             << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
            << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
    
  9. Внутри строки можно, при желании, выполнять выравнивание с помощью пробелов.

    dst.ClickLogID         = click.LogID;
    dst.ClickEventID       = click.EventID;
    dst.ClickGoodEvent     = click.GoodEvent;
    
  10. Вокруг операторов ., -> не ставятся пробелы.

    При необходимости, оператор может быть перенесён на новую строку. В этом случае, перед ним увеличивается отступ.

  11. Унарные операторы (--, ++, *, &, ...) не отделяются от аргумента пробелом.

  12. После запятой ставится пробел, а перед - нет. Аналогично для точки с запятой внутри выражения for.

  13. Оператор [] не отделяется пробелами.

  14. В выражении template <...>, между template и < ставится пробел; после < и до > - не ставится.

    template <typename TKey, typename TValue>
    struct AggregatedStatElement
    {}
    
  15. В классах и структурах, public, private, protected пишется на том же уровне, что и class/struct, а все остальные внутренности - глубже.

    template <typename T, typename Ptr = std::shared_ptr<T>>
    class MultiVersion
    {
    public:
        /// Конкретная версия объекта для использования. shared_ptr определяет время жизни версии.
        using Version = Ptr;
        ...
    }
    
  16. Если на весь файл один namespace и кроме него ничего существенного нет - то отступ внутри namespace не нужен.

  17. Если блок для выражения if, for, while... состоит из одного statement-а, то фигурные скобки писать не обязательно. Вместо этого поместите statement на отдельную строку. Этим statement-ом также может быть вложенный if, for, while... Но если внутренний statement содержит фигурные скобки или else, то у внешнего блок следует писать в фигурных скобках.

    /// Если файлы не открыты, то открываем их.
    if (streams.empty())
        for (const auto & name : column_names)
            streams.emplace(name, std::make_unique<Stream>(
                 storage.files[name].data_file.path(),
                storage.files[name].marks[mark_number].offset));
    
  18. Не должно быть пробелов на концах строк.

  19. Исходники в кодировке UTF-8.

  20. В строковых литералах можно использовать не-ASCII.

    << ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
    
  21. Не пишите несколько выражений в одной строке.

  22. Внутри функций, группируйте куски кода, отделяя их не более, чем одной пустой строкой.

  23. Функции, классы, и т. п. отделяются друг от друга минимум одной, максимум двумя пустыми строками.

  24. const (относящийся к значению) пишется до имени типа.

    //correct
    const char * pos
    const std::string & s
    //incorrect
    char const * pos
    
  25. При объявлении указателя или ссылки, символы * и & отделяются пробелами с обеих сторон.

    //correct
    const char * pos
    //incorrect
    const char* pos
    const char *pos
    
  26. При использовании шаблонных типов, пишите using (кроме, возможно, простейших случаев).

    То есть, параметры шаблона указываются только в using и затем не повторяются в коде.

    using может быть объявлен локально, например, внутри функции.

    //correct
    using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
    FileStreams streams;
    //incorrect
    std::map<std::string, std::shared_ptr<Stream>> streams;
    
  27. Нельзя объявлять несколько переменных разных типов в одном объявлении.

    //incorrect
    int x, *y;
    
  28. C-style cast не используется.

    //incorrect
    std::cerr << (int)c <<; std::endl;
    //correct
    std::cerr << static_cast<int>(c) << std::endl;
    
  29. В классах и структурах, группируйте отдельно методы и отдельно члены, внутри каждой области видимости.

  30. Для не очень большого класса/структуры, можно не отделять объявления методов от реализации.

    Аналогично для маленьких методов в любых классах/структурах.

    Для шаблонных классов/структур, лучше не отделять объявления методов от реализации (так как иначе они всё равно должны быть определены в той же единице трансляции).

  31. Не обязательно умещать код по ширине в 80 символов. Можно в 140.

  32. Всегда используйте префиксный инкремент/декремент, если постфиксный не нужен.

    for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
    

Комментарии

  1. Необходимо обязательно писать комментарии во всех нетривиальных местах.

    Это очень важно. При написании комментария, можно успеть понять, что код не нужен вообще, или что всё сделано неверно.

    /** Part of piece of memory, that can be used.
      * For example, if internal_buffer is 1MB, and there was only 10 bytes loaded to buffer from file for reading,
      * then working_buffer will have size of only 10 bytes
      * (working_buffer.end() will point to position right after those 10 bytes available for read).
      */
    
  2. Комментарии могут быть сколь угодно подробными.

  3. Комментарии пишутся до соответствующего кода. В редких случаях - после, на той же строке.

    /** Parses and executes the query.
    */
    void executeQuery(
        ReadBuffer & istr, /// Where to read the query from (and data for INSERT, if applicable)
        WriteBuffer & ostr, /// Where to write the result
        Context & context, /// DB, tables, data types, engines, functions, aggregate functions...
        BlockInputStreamPtr & query_plan, /// Here could be written the description on how query was executed
        QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// Up to which stage process the SELECT query
        )
    
  4. Комментарии следует писать только на английском языке.

  5. При написании библиотеки, разместите подробный комментарий о том, что это такое, в самом главном заголовочном файле.

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

    /*
    * Procedure Name:
    * Original procedure name:
    * Author:
    * Date of creation:
    * Dates of modification:
    * Modification authors:
    * Original file name:
    * Purpose:
    * Intent:
    * Designation:
    * Classes used:
    * Constants:
    * Local variables:
    * Parameters:
    * Date of creation:
    * Purpose:
    */
    

    (пример взят с ресурса http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/

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

  8. Однострочные комментарии начинаются с трёх слешей: /// , многострочные с /**. Такие комментарии считаются «документирующими».

    Замечание: такие комментарии могут использоваться для генерации документации с помощью Doxygen. Но, фактически, Doxygen не используется, так как для навигации по коду гораздо удобне использовать возможности IDE.

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

  10. Для закомментированных кусков кода, используются обычные, не "документирующие" комментарии.

  11. Удаляйте закомментированные куски кода перед коммитом.

  12. Не нужно писать нецензурную брань в комментариях или коде.

  13. Не пишите прописными буквами. Не используйте излишнее количество знаков препинания.

    /// WHAT THE FAIL???
    
  14. Не составляйте из комментариев строки-разделители.

    ///******************************************************
    
  15. Не нужно писать в комментарии диалог (лучше сказать устно).

    /// Зачем ты сделал эту фигню?
    
  16. Не нужно писать комментарий в конце блока о том, что представлял собой этот блок.

    /// for
    

Имена

  1. Имена переменных и членов класса - маленькими буквами с подчёркиванием.

    size_t max_block_size;
    
  2. Имена функций (методов) - camelCase с маленькой буквы.

    std::string getName() const override { return "Memory"; }
    
  3. Имена классов (структур) - CamelCase с большой буквы. Префиксы кроме I для интерфейсов - не используются.

    class StorageMemory : public IStorage
    
  4. Имена using-ов - также, как классов, либо можно добавить _t на конце.

  5. Имена типов - параметров шаблонов: в простых случаях - T; T, U; T1, T2.

    В более сложных случаях - либо также, как имена классов, либо можно добавить в начало букву T.

    template <typename TKey, typename TValue>
    struct AggregatedStatElement
    
  6. Имена констант - параметров шаблонов: либо также, как имена переменных, либо N - в простом случае.

    template <bool without_www>
    struct ExtractDomain
    
  7. Для абстрактных классов (интерфейсов) можно добавить в начало имени букву I.

    class IBlockInputStream
    
  8. Если переменная используется достаточно локально, то можно использовать короткое имя.

    В остальных случаях - используйте достаточно подробное имя, описывающее смысл.

    bool info_successfully_loaded = false;
    
  9. define-ы - ALL_CAPS с подчёркиванием. Глобальные константы - тоже.

    #define MAX_SRC_TABLE_NAMES_TO_STORE 1000
    
  10. Имена файлов с кодом называйте по стилю соответственно тому, что в них находится.

    Если в файле находится один класс - назовите файл, как класс - в CamelCase.

    Если в файле находится одна функция - назовите файл, как функцию - в camelCase.

  11. Если имя содержит сокращение, то:

    • для имён переменных, всё сокращение пишется маленькими буквами mysql_connection (не mySQL_connection).
    • для имён классов и функций, сохраняются большие буквы в сокращении MySQLConnection (не MySqlConnection).
  12. Параметры конструктора, использующиеся сразу же для инициализации соответствующих членов класса, следует назвать также, как и члены класса, добавив подчёркивание в конец.

    FileQueueProcessor(
        const std::string & path_,
        const std::string & prefix_,
        std::shared_ptr<FileHandler> handler_)
        : path(path_),
        prefix(prefix_),
        handler(handler_),
        log(&Logger::get("FileQueueProcessor"))
    {
    }
    

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

  13. Именование локальных переменных и членов класса никак не отличается (никакие префиксы не нужны).

    timer (не m_timer)
    
  14. Константы в enum-е - CamelCase с большой буквы. Также допустимо ALL_CAPS. Если enum не локален, то используйте enum class.

    enum class CompressionMethod
    {
        QuickLZ = 0,
        LZ4     = 1,
    };
    
  15. Все имена - по английски. Транслит с русского использовать нельзя.

    не Stroka
    
  16. Сокращения (из нескольких букв разных слов) в именах можно использовать только если они являются общепринятыми (если для сокращения можно найти расшифровку в английской википедии или сделав поисковый запрос).

    AST, SQL.

    Не NVDH (что-то неведомое)

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

    Впрочем, сокращения также можно использовать, если расшифровка находится рядом в комментарии.

  17. Имена файлов с исходниками на C++ должны иметь расширение только .cpp. Заголовочные файлы - только .h.

Как писать код

  1. Управление памятью.

    Ручное освобождение памяти (delete) можно использовать только в библиотечном коде.

    В свою очередь, в библиотечном коде, оператор delete можно использовать только в деструкторах.

    В прикладном коде следует делать так, что память освобождается каким-либо объектом, который владеет ей.

    Примеры:

    • проще всего разместить объект на стеке, или сделать его членом другого класса.
    • для большого количества маленьких объектов используйте контейнеры.
    • для автоматического освобождения маленького количества объектов, выделенных на куче, используйте shared_ptr/unique_ptr.
  2. Управление ресурсами.

    Используйте RAII и см. пункт выше.

  3. Обработка ошибок.

    Используйте исключения. В большинстве случаев, нужно только кидать исключения, а ловить - не нужно (потому что RAII).

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

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

    В функциях потока, следует ловить и запоминать все исключения, чтобы выкинуть их в основном потоке после join.

    /// Если вычислений ещё не было - вычислим первый блок синхронно
    if (!started)
    {
        calculate();
        started = true;
    }
    else    /// Если вычисления уже идут - подождём результата
        pool.wait();
    
    if (exception)
        exception->rethrow();
    

    Ни в коем случае не «проглатывайте» исключения без разбора. Ни в коем случае, не превращайте все исключения без разбора в сообщения в логе.

    Не catch (...) {}.

    Если вам нужно проигнорировать какие-то исключения, то игнорируйте только конкретные, а остальные - кидайте обратно.

    catch (const DB::Exception & e)
    {
        if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
            return nullptr;
        else
            throw;
    }
    

    При использовании функций, использующих коды возврата или errno - проверяйте результат и кидайте исключение.

    if (0 != close(fd))
        throwFromErrno("Cannot close file " + file_name, ErrorCodes::CANNOT_CLOSE_FILE);
    

    assert-ы не используются.

  4. Типы исключений.

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

  5. Исключения, вылетающие из деструкторов. Использовать не рекомендуется, но допустимо.

    Используйте следующие варианты:

    • Сделайте функцию (done() или finalize()), которая позволяет заранее выполнить всю работу, в процессе которой может возникнуть исключение. Если эта функция была вызвана, то затем в деструкторе не должно возникать исключений.
    • Слишком сложную работу (например, отправку данных по сети) можно вообще не делать в деструкторе, рассчитывая, что пользователь заранее позовёт метод для завершения работы.
    • Если в деструкторе возникло исключение, желательно не "проглатывать" его, а вывести информацию в лог (если в этом месте доступен логгер).
    • В простых программах, если соответствующие исключения не ловятся, и приводят к завершению работы с записью информации в лог, можно не беспокоиться об исключениях, вылетающих из деструкторов, так как вызов std::terminate (в случае noexcept по умолчанию в C++11), является приемлимым способом обработки исключения.
  6. Отдельные блоки кода.

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

    Block block = data.in->read();
    
    {
        std::lock_guard<std::mutex> lock(mutex);
        data.ready = true;
        data.block = block;
    }
    
    ready_any.set();
    
  7. Многопоточность.

    В программах offline обработки данных:

    • ачала добейтесь более-менее максимальной производительности на одном процессорном ядре, потом можно распараллеливать код, но только если есть необходимость.

    В программах - серверах:

    • используйте пул потоков для обработки запросов. На данный момент, у нас не было задач, в которых была бы необходимость использовать userspace context switching.

    Fork для распараллеливания не используется.

  8. Синхронизация потоков.

    Часто можно сделать так, чтобы отдельные потоки писали данные в разные ячейки памяти (лучше - в разные кэш-линии), и не использовать синхронизацию потоков (кроме joinAll).

    Если синхронизация нужна, то в большинстве случаев, достаточно использовать mutex под lock_guard-ом.

    В остальных случаях, используйте системные примитивы синхронизации. Не используйте busy wait.

    Атомарные операции можно использовать только в простейших случаях.

    Не нужно писать самостоятельно lock-free структуры данных, если вы не являетесь экспертом.

  9. Ссылки и указатели.

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

  10. const.

    Используйте константные ссылки, указатели на константу, const_iterator, константные методы.

    Считайте, что const - вариант написания «по умолчанию», а отсутствие const - только при необходимости.

    Для переменных, передающихся по значению, использовать const обычно не имеет смысла.

  11. unsigned.

    Используйте unsigned, если нужно.

  12. Числовые типы.

    Используйте типы UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64, а также size_t, ssize_t, ptrdiff_t.

    Не используйте для чисел типы signed/unsigned long, long long, short; signed char, unsigned char, а также char.

  13. Передача аргументов.

    Сложные значения передавайте по ссылке (включая std::string).

    Если функция захватывает владение объектом, созданным на куче, то сделайте типом аргумента shared_ptr или unique_ptr.

  14. Возврат значений.

    В большинстве случаев, просто возвращайте значение с помощью return. Не пишите [return std::move(res)]{.strike}.

    Если внутри функции создаётся объект на куче и отдаётся наружу, то возвращайте shared_ptr или unique_ptr.

    В некоторых редких случаях, может потребоваться возвращать значение через аргумент функции. В этом случае, аргументом будет ссылка.

    using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;
    
    /** Позволяет создать агрегатную функцию по её имени.
      */
    class AggregateFunctionFactory
    {
    public:
        AggregateFunctionFactory();
        AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
    
  15. namespace.

    Для прикладного кода отдельный namespace использовать не нужно.

    Для маленьких библиотек - не требуется.

    Для не совсем маленьких библиотек - поместите всё в namespace.

    Внутри библиотеки в .h файле можно использовать namespace detail для деталей реализации, не нужных прикладному коду.

    В .cpp файле можно использовать static или анонимный namespace для скрытия символов.

    Также, namespace можно использовать для enum, чтобы соответствующие имена не попали во внешний namespace (но лучше использовать enum class).

  16. Отложенная инициализация.

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

    Если потом вам потребовалась отложенная инициализация, то вы можете дописать конструктор по умолчанию (который создаст объект с некорректным состоянием). Или, для небольшого количества объектов, можно использовать shared_ptr/unique_ptr.

    Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);
    
    /// Для отложенной инициализации
    Loader() {}
    
  17. Виртуальные функции.

    Если класс не предназначен для полиморфного использования, то не нужно делать функции виртуальными зря. Это относится и к деструктору.

  18. Кодировки.

    Везде используется UTF-8. Используется std::string, char *. Не используется std::wstring, wchar_t.

  19. Логгирование.

    См. примеры везде в коде.

    Перед коммитом, удалите всё бессмысленное и отладочное логгирование, и другие виды отладочного вывода.

    Не должно быть логгирования на каждую итерацию внутреннего цикла, даже уровня Trace.

    При любом уровне логгирования, логи должно быть возможно читать.

    Логгирование следует использовать, в основном, только в прикладном коде.

    Сообщения в логе должны быть написаны на английском языке.

    Желательно, чтобы лог был понятен системному администратору.

    Не нужно писать ругательства в лог.

    В логе используется кодировка UTF-8. Изредка можно использовать в логе не-ASCII символы.

  20. Ввод-вывод.

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

    Вместо этого используйте библиотеку DB/IO.

  21. Дата и время.

    См. библиотеку DateLUT.

  22. include.

    В заголовочном файле используется только #pragma once, а include guard-ы писать не нужно.

  23. using.

    using namespace не используется.

    using что-то конкретное - можно. Лучше локально - внутри класса или функции.

  24. Не нужно использовать trailing return type для функций, если в этом нет необходимости.

    [auto f() -> void;]{.strike}

  25. Не нужно объявлять и инициализировать переменные так:

    auto s = std::string{"Hello"};
    

    Надо так:

    std::string s = "Hello";
    std::string s{"Hello"};
    
  26. Для виртуальных функций, пишите virtual в базовом классе, а в классах-наследниках, пишите override и не пишите virtual.

Неиспользуемые возможности языка C++

  1. Виртуальное наследование не используется.
  2. Спецификаторы исключений из C++03 не используются.
  3. Function try block не используется, за исключением функции main в тестах.

Платформа

  1. Мы пишем некроссплатформенный код (под конкретную платформу).

    Хотя, при прочих равных условиях, предпочитается более-менее кроссплатформенный или легко портируемый код.

  2. Язык - C++17. Возможно использование расширений GNU при необходимости.

  3. Компилятор - gcc. На данный момент (апрель 2017), код собирается версией 6.3. (Также код может быть собран clang 4)

    Используется стандартная библиотека от gcc.

  4. ОС - Linux Ubuntu, не более старая, чем Precise.

  5. Код пишется под процессор с архитектурой x86_64.

    Набор инструкций - минимальный поддерживаемый среди наших серверов. Сейчас это - SSE4.2.

  6. Используются флаги компиляции -Wall -Werror.

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

  8. Код разрабатывается и отлаживается с релизными параметрами сборки.

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

  1. Хорошая среда разработки - KDevelop.

  2. Для отладки используется gdb, valgrind (memcheck), strace, -fsanitize=..., tcmalloc_minimal_debug.

  3. Для профилирования используется Linux Perf, valgrind (callgrind), strace -cf.

  4. Исходники в Git.

  5. Сборка с помощью CMake.

  6. Программы выкладываются с помощью deb пакетов.

  7. Коммиты в master не должны ломать сборку проекта.

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

  8. Коммитьте как можно чаще, в том числе и не рабочий код.

    Для этого следует использовать бранчи.

    Если ваш код в master-е ещё не собирается, перед push-ем - исключите его из сборки, также вы будете должны его доработать или удалить в течение нескольких дней.

  9. Для нетривиальных изменений, используются бранчи. Следует загружать бранчи на сервер.

  10. Ненужный код удаляется из исходников.

Библиотеки

  1. Используются стандартная библиотека C++14 (допустимо использовать experimental расширения) а также фреймворки boost, Poco.

  2. При необходимости, можно использовать любые известные библиотеки, доступные в ОС из пакетов.

    Если есть хорошее готовое решение, то оно используется, даже если для этого придётся установить ещё одну библиотеку.

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

  3. Если в пакетах нет нужной библиотеки, или её версия достаточно старая, или если она собрана не так, как нужно, то можно использовать библиотеку, устанавливаемую не из пакетов.

  4. Если библиотека достаточно маленькая и у неё нет своей системы сборки, то следует включить её файлы в проект, в директорию contrib.

  5. Предпочтение всегда отдаётся уже использующимся библиотекам.

Общее

  1. Пишите как можно меньше кода.
  2. Пробуйте самое простое решение.
  3. Не нужно писать код, если вы ещё не знаете, что будет делать ваша программа, и как будет работать её внутренний цикл.
  4. В простейших случаях, используйте using вместо классов/структур.
  5. Если есть возможность - не пишите конструкторы копирования, операторы присваивания, деструктор (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), move-конструкторы и move-присваивания. То есть, чтобы соответствущие функции, генерируемые компилятором, работали правильно. Можно использовать default.
  6. Приветствуется упрощение и уменьшение объёма кода.

Дополнительно

  1. Явное указание std:: для типов из stddef.h.

    Рекомендуется не указывать. То есть, рекомендуется писать size_t вместо std::size_t - потому что это короче.

    Но при желании, вы можете всё-таки приписать std:: - такой вариант тоже допустим.

  2. Явное указание std:: для функций из стандартной библиотеки C.

    Не рекомендуется. То есть, пишите memcpy вместо std::memcpy.

    Причина - существуют похожие нестандартные функции, например, memmem. Мы можем использовать и изредка используем эти функции. Эти функции отсутствуют в namespace std.

    Если вы везде напишете std::memcpy вместо memcpy, то будет неудобно смотреться memmem без std::.

    Тем не менее, указывать std:: тоже допустимо, если так больше нравится.

  3. Использование функций из C при наличии аналогов в стандартной библиотеке C++.

    Допустимо, если это использование эффективнее.

    Для примера, для копирования длинных кусков памяти, используйте memcpy вместо std::copy.

  4. Перенос длинных аргументов функций.

    Допустимо использовать любой стиль переноса, похожий на приведённые ниже:

    function(
        T1 x1,
        T2 x2)
    
    function(
        size_t left, size_t right,
        const & RangesInDataParts ranges,
        size_t limit)
    
    function(size_t left, size_t right,
        const & RangesInDataParts ranges,
        size_t limit)
    
    function(size_t left, size_t right,
            const & RangesInDataParts ranges,
            size_t limit)
    
    function(
            size_t left,
            size_t right,
            const & RangesInDataParts ranges,
            size_t limit)