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

Общие рекомендации

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

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

1. Большая часть форматирования выполняется автоматически с помощью clang-format. 2. Отступ — 4 пробела. Настройте среду разработки так, чтобы нажатие Tab добавляло четыре пробела. 3. Открывающие и закрывающие фигурные скобки должны располагаться на отдельной строке.
inline void readBoolText(bool & x, ReadBuffer & buf)
{
    char tmp = '0';
    readChar(tmp, buf);
    x = tmp != '0';
}
4. Если всё тело функции представляет собой один оператор, его можно записать в одну строку. Ставьте пробелы вокруг фигурных скобок (кроме пробела в конце строки).
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>
class MultiVersion
{
public:
    /// Версия объекта для использования. shared_ptr управляет временем жизни версии.
    using Version = std::shared_ptr<const T>;
    ...
}
16. Если одно и то же namespace используется во всём файле и больше ничего значимого нет, смещение внутри namespace не требуется. 17. Если блок для if, for, while или другого выражения состоит из одного оператора, фигурные скобки необязательны. В таком случае поместите оператор на отдельную строку. Это правило также распространяется на вложенные if, for, while, … Но если внутренний оператор содержит фигурные скобки или else, внешний блок необходимо заключить в фигурные скобки.
/// Завершить запись.
for (auto & stream : streams)
    stream.second->finalize();
18. В конце строк не должно быть пробелов. 19. Исходные файлы имеют кодировку UTF-8. 20. В строковых литералах допускается использование символов, не входящих в набор ASCII.
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
21. Не пишите несколько выражений в одной строке. 22. Группируйте части кода внутри функций и разделяйте их не более чем одной пустой строкой. 23. Разделяйте функции, классы и т. д. одной или двумя пустыми строками. 24. A const (относящийся к значению) должен записываться перед именем типа.
//правильно
const char * pos
const std::string & s
//неправильно
char const * pos
25. При объявлении указателя или ссылки символы * и & должны быть отделены пробелами с обеих сторон.
//правильно
const char * pos
//неправильно
const char* pos
const char *pos
26. При использовании шаблонных типов задавайте для них псевдонимы с помощью ключевого слова using (кроме самых простых случаев). Иными словами, параметры шаблона указываются только в using и не дублируются в коде. using можно объявлять локально, например внутри функции.
//правильно
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
FileStreams streams;
//неправильно
std::map<std::string, std::shared_ptr<Stream>> streams;
27. Не объявляйте в одном операторе несколько переменных разных типов.
//неправильно
int x, *y;
28. Не используйте приведение типов в стиле C.
//неправильно
std::cerr << (int)c <<; std::endl;
//правильно
std::cerr << static_cast<int>(c) << std::endl;
29. В классах и struct группируйте члены и функции отдельно в пределах каждой области видимости. 30. В небольших классах и struct не обязательно отделять объявление метода от его реализации. То же самое верно и для небольших методов в любых классах или struct. Для шаблонных классов и struct не отделяйте объявления методов от реализации (иначе их придётся определять в той же единице трансляции). 31. Можно переносить строки по достижении 140 символов, а не 80. 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. Размещайте комментарии перед кодом, к которому они относятся. В редких случаях комментарий можно разместить после кода, в той же строке.
/** Разбирает и выполняет запрос.
*/
void executeQuery(
    ReadBuffer & istr, /// Откуда читать запрос (и данные для INSERT, если применимо)
    WriteBuffer & ostr, /// Куда записывать результат
    Context & context, /// БД, таблицы, типы данных, движки, функции, агрегатные функции...
    BlockInputStreamPtr & query_plan, /// Здесь может быть записано описание того, как выполнялся запрос
    QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// До какой стадии обрабатывать запрос SELECT
    )
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. Не разводите дискуссии в комментариях.
/// Why did you do this stuff?
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 именуются так же, как и классы. 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 IProcessor
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 (not m_timer)
14. Для констант в enum используйте CamelCase с заглавной буквы. Также допустим стиль ALL_CAPS. Если enum не является локальным, используйте enum class.
enum class CompressionMethod
{
    QuickLZ = 0,
    LZ4     = 1,
};
15. Все имена должны быть на английском языке. Транслитерация слов с иврита не допускается. не T&#95;PAAMAYIM&#95;NEKUDOTAYIM 16. Сокращения допустимы, если они общеупотребительны (то есть их значение можно легко найти в Википедии или через поисковую систему). AST, SQL. Не NVDH (какой-то случайный набор букв) Усечённые слова допустимы, если сокращённый вариант широко используется. Также можно использовать сокращение, если рядом с ним в комментариях указано полное название. 17. Имена файлов с исходным кодом C++ должны иметь расширение .cpp. Файлы заголовков должны иметь расширение .h.

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

1. Управление памятью. Ручное освобождение памяти (delete) можно использовать только в библиотечном коде. В библиотечном коде оператор delete можно использовать только в деструкторах. В прикладном коде память должен освобождать объект, который ею владеет. Примеры:
  • Проще всего разместить объект на стеке или сделать его членом другого класса.
  • Для большого количества небольших объектов используйте контейнеры.
  • Для автоматического освобождения небольшого числа объектов, размещённых в куче, используйте shared_ptr/unique_ptr.
2. Управление ресурсами. Используйте RAII; см. выше. 3. Обработка ошибок. Используйте исключения. В большинстве случаев достаточно просто сгенерировать исключение, а перехватывать его не требуется (благодаря RAII). В приложениях для офлайн-обработки данных часто допустимо не перехватывать исключения. В серверах, обрабатывающих пользовательские запросы, обычно достаточно перехватывать исключения на верхнем уровне обработчика соединения. В функциях потоков следует перехватывать и сохранять все исключения, чтобы повторно выбросить их в главном потоке после 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))
    throw ErrnoException(ErrorCodes::CANNOT_CLOSE_FILE, "Cannot close file {}", file_name);
Вы можете использовать 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. Многопоточность. В программах для офлайн-обработки данных:
  • Старайтесь добиться максимально возможной производительности на одном ядре CPU. Затем при необходимости распараллельте код.
В серверных приложениях:
  • Используйте пул потоков для обработки запросов. Пока у нас не было задач, требующих переключения контекста в пространстве пользователя.
Fork не используют для распараллеливания. 8. Синхронизация потоков. Часто можно сделать так, чтобы разные потоки использовали разные ячейки памяти (а ещё лучше — разные линии кэша) и вообще не использовать синхронизацию потоков (кроме joinAll). Если синхронизация нужна, в большинстве случаев достаточно использовать mutex с lock_guard. В остальных случаях используйте системные примитивы синхронизации. Не используйте активное ожидание. Атомарные операции следует использовать только в самых простых случаях. Не пытайтесь реализовывать lock-free-структуры данных, если это не ваша основная область экспертизы. 9. Указатели и ссылки. В большинстве случаев предпочитайте ссылки. 10. const. Используйте константные ссылки, указатели на константы, const_iterator и const-методы. Считайте 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/unsigned char, char. 13. Передача аргументов. Передавайте сложные значения по значению, если затем они будут перемещены, и используйте std::move; передавайте по ссылке, если хотите обновлять значение в цикле. Если функция принимает владение объектом, созданным в куче, используйте для аргумента тип shared_ptr или unique_ptr. 14. Возвращаемые значения. В большинстве случаев просто используйте return. Не пишите return std::move(res). Если функция выделяет объект в куче и возвращает его, используйте 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. Логирование. См. примеры в коде. Перед commit удаляйте все бессмысленные и отладочные записи в журнале, а также любые другие виды отладочного вывода. Следует избегать логирования в циклах, даже на уровне Trace. Журнал должен оставаться читаемым при любом уровне логирования. Логирование по большей части следует использовать только в прикладном коде. Сообщения лога должны быть написаны на английском языке. Желательно, чтобы журнал был понятен системному администратору. Не используйте нецензурную лексику в журнале. Используйте в журнале кодировку UTF-8. В редких случаях в журнале можно использовать символы не из ASCII. 20. Ввод-вывод. Не используйте iostreams во внутренних циклах, критичных для производительности приложения (и никогда не используйте stringstream). Вместо этого используйте библиотеку DB/IO. 21. Дата и время. См. библиотеку DateLUT. 22. include. Всегда используйте #pragma once вместо include guards. 23. using. using namespace не используется. Можно использовать using для чего-то конкретного, но делайте это локально — внутри класса или функции. 24. Не используйте для функций trailing return type, если в этом нет необходимости.
auto f() -> void
25. Объявление и инициализация переменных.
//правильно
std::string s = "Hello";
std::string s{"Hello"};

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

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

1. Виртуальное наследование не используется. 2. Конструкции, для которых в современном C++ предусмотрен удобный синтаксический сахар, например:
// Традиционный способ без синтаксического сахара
template <typename G, typename = std::enable_if_t<std::is_same<G, F>::value, void>> // SFINAE через std::enable_if, использование ::value
std::pair<int, int> func(const E<G> & e) // явно указанный тип возвращаемого значения
{
    if (elements.count(e)) // проверка вхождения через .count()
    {
        // ...
    }

    elements.erase(
        std::remove_if(
            elements.begin(), elements.end(),
            [&](const auto x){
                return x == 1;
            }),
        elements.end()); // идиома remove-erase

    return std::make_pair(1, 2); // создание пары через make_pair()
}

// С синтаксическим сахаром (C++14/17/20)
template <typename G>
requires std::same_v<G, F> // SFINAE через концепт C++20, использование псевдонима шаблона C++14
auto func(const E<G> & e) // вывод типа возвращаемого значения через auto (C++14)
{
    if (elements.contains(e)) // проверка вхождения через .contains в C++20
    {
        // ...
    }

    elements.erase_if(
        elements,
        [&](const auto x){
            return x == 1;
        }); // std::erase_if в C++20

    return {1, 2}; // или: return std::pair(1, 2); // создание пары через список инициализации или инициализацию значением (C++17)
}

Платформа

1. Мы пишем код для конкретной платформы. Но при прочих равных предпочтение отдаётся кроссплатформенному или переносимому коду. 2. Язык: C++20 (см. список доступных возможностей C++20). 3. Компилятор: clang. На момент написания (март 2025 года) код компилируется с помощью clang версии >= 19. Используется стандартная библиотека (libc++). 4. ОС: Ubuntu Linux, не старее Precise. 5. Код пишется для архитектуры CPU x86_64. Набор инструкций CPU — минимально поддерживаемый среди наших серверов. В настоящее время это SSE 4.2. 6. Используйте флаги компиляции -Wall -Wextra -Werror -Weverything с несколькими исключениями. 7. Используйте статическую компоновку со всеми библиотеками, кроме тех, которые трудно подключить статически (см. вывод команды ldd). 8. Код разрабатывается и отлаживается в release-конфигурации.

Инструменты

1. KDevelop — хорошая IDE. 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++20 (допускаются экспериментальные расширения), а также boost и Poco. 2. Запрещено использовать библиотеки из пакетов операционной системы. Также запрещено использовать предустановленные библиотеки. Все библиотеки должны поставляться в виде исходного кода в каталоге contrib и собираться вместе с ClickHouse. Подробности см. в рекомендациях по добавлению новых сторонних библиотек. 3. Предпочтение всегда отдается библиотекам, которые уже используются.

Общие рекомендации

1. Пишите как можно меньше кода. 2. Старайтесь выбирать самое простое решение. 3. Не пишите код, пока не поймете, как он должен работать и как будет устроен внутренний цикл. 4. В самых простых случаях используйте using вместо классов или структур. 5. По возможности не пишите конструкторы копирования, операторы присваивания, деструкторы (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), конструкторы перемещения или операторы присваивания перемещением. Иными словами, функции, сгенерированные компилятором, должны работать корректно. Можно использовать 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)
Последнее изменение 10 июня 2026 г.