Все мы при написании кода пользуемся правилами оформления кода. Иногда изобретаются свои правила, в других случаях используются готовые стайлгайды. Однако, любой стайлгайд со временем корректируется и дорабатывается: иногда этому способствуют обновление стандартов языка, иногда меняются тенденции.
В статье приведены изменения Руководства Google по стилю в C++ за 5 лет: с 2019 по 2024.
Краткое содержание изменений:
+ C++20 - NULL + концепты - #pragma + constinit - std:hash + consteval - u8 + аргументы-ссылки - ENUM_VALUE_NAME + повесточка и "they" в единственном числе - здравый смысл
Изменения в «Руководстве» по стилю можно разделить на три направления:
1. Переход на C++20
Основной стандарт языка в Руководстве теперь от 20-го года и отныне код должен соответствовать C++20. Однако, это совсем не означает, что можно использовать новые возможности.
В частности, если планируется портирование кода для различных платформ, то следует оценить целесообразность применения новых возможностей как C++17, так и C++20 и, в некоторых случаях, даже отказаться от них. Но в любом случае создаваемый код должен корректно работать в C++20 (и в качестве примера смотрите про запрет префикса u8 ниже).
Если же C++20 допустим в проекте, то «Руководство» предлагает использовать новые возможности:
— Используйте constinit и consteval
Заметим, что constinit и раньше предлагался к использованию, но только в форме атрибута ABSL_CONST_INIT от внешней кодовой базы. Теперь constinit уже официально внесён в стандарт и рекомендуется к применению.
Что касается consteval, то если вы желаете чтобы функция вычислялась только при компиляции, то лучше применять consteval. А если единственное желание это сделать функцию встраиваемой — то не применять.
— Разрешён оператор трёхстороннего сравнения (оператор «космокорабль», эквивалентности). Его рекомендуется использовать только в тех случаях, когда для двух значений некоторого типа соотношение больше/меньше является очевидным, ясным и единственным. И если всё это выполняется, то рекомендуется определить два оператора: == и <=>. Эти определения должны быть непротиворечивы в части сравнения на равенство. А от остальных операторов сравнения следует избавиться.
С другой стороны, если отношение больше/меньше не такое уж очевидное, или возможно несколько вариантов, то не определяйте оператор <=>.
— Добавилась возможность назначенной инициализации («Designated_initializers»). Назначенная инициализация ранее считалась нестандартным расширением и не рекомендовалась к использованию. После появления в стандарте её можно использовать, но только в виде, совместимом с C++20. Например:
struct Point { float x = 0.0; float y = 0.0; float z = 0.0; }; Point p = { .x = 1.0, .y = 2.0, // z будет 0.0 };
Концепты и Ограничения. В «Руководстве» разрешили использовать концепты с ограничениями и сразу сделали дополнительный раздел о том, что такое концепты/ограничения, что можно с ними делать, что нежелательно, какие здесь плюсы и минусы. В целом, всё соответствует логике, но, например, такая форма записи:
template<Concept T>не рекомендуется.
И вообще, «Руководство» призывает «используйте концепты в разумных количествах, без фанатизма».
2. От чистого C в более безопасный C++
В этой части «Руководство» постаралось перенять хорошие практики, которые ранее были запрещены, и запретить что-то совсем устаревшее.
Ссылочные выходные параметры: одно из явных изменений связано с использованием ссылочных аргументов для выдачи результата (как выходные параметры). Если в ранних версиях «Руководства» ссылки могли использоваться только как входные параметры (в виде константных ссылок), то теперь ссылки используются и для возврата значений в обязательных (т.е. не опциональных) параметрах. Обычные указатели тоже не забыты и всё ещё применяются для возврата значений в опциональных параметрах (для которых допустимо передавать nullptr). И параллельно для опциональных параметров предлагается использовать std::optional — это такая современная альтернатива.
#pragma объявлена недопустимой и не должна использоваться, т.к. это нестандартное расширение. Причём в ранних версиях «Руководства» про неё упоминалось вскользь и только в разделе про программирование под Windows. Теперь это стало общим правилом. С учётом запрета на #pragma once и постепенному переходу к правильно выровненным данным (и сомнительной полезности #pragma pack в этом случае) это может быть логичным решением. Особенно учитывая построение под различные платформы и с применением разных компиляторов.
NULL — конечно же, он тоже теперь под запретом. Если раньше в «Руководстве» ещё были упоминания про C++03 и допускалось использование NULL для совместимости, то теперь есть только nullptr.
раздел std::hash вообще удалён из «Руководства» — видимо, чтобы даже мыслей не было про собственные пользовательские специализации. В целом, наверное это правильное движение, так как создание нормальной хэш-функции для специфических типов может быть совсем нетривиальной задачей.
использование #include-ов ещё больше регламентировано. Во-первых, рекомендуется подключать все используемые заголовочные файлы, не полагаться на вложенные #include-ы (ситуация, когда один заголовочный файл включает другой, тот включает третий и т.д.) и это должно сделать использование заголовочных файлов более самодостаточным и безопасным. Вообще это может быть здравой идеей, но далеко не всегда: например, если вы используете библиотеку, у которой общий заголовочный файл со списком #include-ов (это очень частое решение), то вместо одного общего файла как-бы рекомендуется прописывать весь список — и это ну очень неоднозначное решение.
Также про включение заголовочных файлов есть интересная рекомендация стараться указывать имя файла именно в кавычках (а угловые скобки использовать только по явной необходимости; системные файлы являются такой необходимостью). Такой подход может быть, наверное, полезным, так как пути системных заголовочных файлов всё равно будут использованы в поиске, а возможное упрощение может снизить количество ошибок.
В плане безопасного применения C++ есть тоже подвижки:
enum class: теперь энумераторы объявляются в форме «с областью видимости»:
enum class UrlTableError { ... }и это может быть полезным в больших кодовых базах.
std::unique_ptr постепенно заменяет обычные указатели, и такой подход тоже может сделать код более безопасным.
Пока эти изменения не указаны как явные рекомендации в тексте, но примеры кода уже обновлены (т.е. существующий код пока можно не переписывать, однако скоро придётся).
constexpr string_view предлагается как предпочтительный тип для глобальных или статических строковых констант. Символьный массив или указатель на первый символ строкового литерала тоже пока ещё допустимы, но направление движения уже в сторону средств C++.
std::bit_cast рекомендуется для приведения типов (ранее рекомендовалось использование absl::bit_cast) и в свете увеличивающегося количества UB это логичное требование.
absl::implicit_cast рекомендуется для приведения типов вверх по иерархии классов. Оно, конечно, и само приводится, но так безопаснее (возможно) и легче ищется по коду (это более вероятно).
переменные thread_local используются всё чаще и в «Руководстве» расширено описание по использованию: расписано как их применять, на что обратить внимание (спойлер: на порядок переменных и их деструкторы: потоки могут создаваться и завершаться часто, деструкторы вызываются постоянно, и для корректной работы всё должно быть в правильном порядке), и инициализировать их в ряде случаев нужно как constinit. В общем, «Руководство» проводит разъяснительную работу и заботится о безопасности кода.
префикс u8 умудрился попасть под запрет: сейчас по возможности избегайте его использования. Префикс u8 стал «нехорошим» после того, как стандартизаторы многое перекроили и теперь в разных стандартах при использовании префикса u8 создаются массивы из символов разного типа: где-то это char[], где-то char8_t[]. Так как есть подозрение, что в следующем стандарте опять могут что-то поменять, то решили, что лучше без префикса.
3. Именование, Комментарии, Пунктуация
В этом направлении «Руководство» тоже обновилось. В целом это ожидаемо, так как основная цель «правильного» написания кода это облегчить его чтение и поддержку.
Именование постепенно уходит от привычек чистого C:
— имена в перечислениях в прописном регистре (ENUM_NAME) теперь применять нельзя. Нужно использовать именование в стиле обычной константы: kEnumName.
— начинается «выдавливание» макросов препроцессора из кода. Пока делаются мягкие воздействия: в макросах препроцессора рекомендуется использовать название проекта, например так:
#define MYPROJECT_ROUND(x)
— добавились правила именования концептов.
Расстановку пробелов и переводов строки переписали и сделали «алгоритм». Раньше это был набор примеров, теперь всё формализовали и ввели понятия: оператор, итерация, лексема, здесь ставим пробел, а здесь перевод строки. В общем намудрили, но, похоже, это вынужденно, так как когда в блоке условия пишется много разного:
} else if (int a = f(); a != 3) {
одними примерами уже не обойтись. К счастью, примеры пока ещё тоже остались: можно по ним изучать пунктуационную мысль.
Также явно обозвали стиль именования «маленькие буквы; слова через подчёркивание»: теперь это называется змеиный стиль.
Повесточка начинает влиять на код. В целом, это было очевидно, что желание не обидеть доберётся и до оформления кода. Вот теперь добралось:
Добавлен раздел про «Инклюзивный Язык», в котором нам объясняют, что использование терминов «белый список» и «чёрный список» не приветствуется, также как и многих других. И вместо местоимений «он» или «она» нужно использовать ангийский «they» или «their» в качестве местоимения единственного числа (видимо, на манер устаревшего местоимения «онЕ»).
Минус Здравый Смысл: к сожалению, был выкинут заключительный раздел, где рекомендовали руководствоваться здравым смыслом и придерживаться постоянства в кодировании. Выкинут целиком. Вот так.
Итог:
Идёт постепенный разворот от чистого C в сторону безопасного и толерантного C++. И «Руководство» есть отражение этого.Ссылки на версии «Руководства»:
Руководство Google по стилю в C++ 2019: Серия статей на Хабре
Руководство Google по стилю в C++ 2024