что такое std в английском
Что не так с std::visit в современном C++
Сигма-тип и вы
Давайте поговорим о простой, но мощной концепции в программировании — сигма-типах.
Сигма-тип (тип-сумма, меченное объединение) может содержать значения одного и только одного из нескольких типов. Например, рассмотрим настройки в INI-подобном файле конфигурации. Пусть каждая настройка может быть строкой, целым или булевым значением. Если бы мы хотели сделать свою библиотеку на C++, мы бы написали что-то вроде этого:
Это опасный путь, ибо всегда нужно помнить о нескольких вещах:
Было бы куда лучше, если бы сигма-тип общего назначения был в стандартной библиотеке. В C++17 мы наконец его получили! Он называется std::variant, и сейчас мы познакомимся с ним поближе.
Использование std::variant
Как определить объект-посетитель? Один из способов — создать объект, в котором оператор вызова перегружен для всех нужных типов:
Выглядит ужасно многословно, и становится ещё хуже, если нам нужно захватить или изменить какое-то внешнее состояние. Хмм… лямбды великолепно справляются с захватом состояния. Может можно сделать из них объект-посетитель?
Это выглядит хлопотно, но не отчаивайтесь! C++17 вводит новый синтаксис, который сократит весь этот код до
Просто, не правда ли? Если же вам не по душе ни один из этих вариантов, воспользуйтесь условным оператором времени компиляции (if constexpr) из стандарта C++17:
Вся эта канитель для std::visit — полнейшее безумие. Мы начали с простой цели: посмотреть на содержимое сигма-типа. А чтобы завершить эту скромную миссию, нам пришлось:
Как мы вообще тут оказались?
И всё же как мы дошли до жизни такой? Я предполагаю, что дело в психологической склонности человека к подтверждению своей точки зрения. Возможно, когда несколько достаточно грамотных людей, которые знают как работает SFINAE, и не пугаются при виде чего-то такого:
Я не буду говорить, что переусложнённость C++ является благом, но он явно гораздо сложнее, чем следовало бы. Скотт Мейерс, автор книг Effective C++ и Effective Modern C++, тоже высказал подобные мысли в недавних лекциях. Вслед за Мейерсом, я уверен, что каждый член комитета всеми силами старается избегать ненужной сложности и сделать язык как можно проще в использовании. Но это трудно сказать, глядя на результат их работы. Беспричинная сложность продолжает нарастать.
Куда мы движемся?
Есть причина, по которой C++ используется настолько широко, особенно в системном программировании (*). Он чрезвычайно выразителен, оставляя при этом полный контроль над аппаратурой. Инструментарий, созданный для C++, является наиболее развитым среди всех языков программирования, кроме C. Он поддерживает невообразимое количество платформ.
Глядя на то, что оказывается в стандарте и то, что мы слышим на конференциях, создаётся впечатление, что комитет пытается преодолеть эти недостатки, понемногу перетаскивая хорошие решения из других языков. На первый взгляд хорошая идея, но новые фичи часто приходят в язык недопечёнными. Хотя C++ не собирается на покой в ближайшее время, он будто бы постоянно неуклюже остаётся в роли догоняющего.
Примечания
1. Термин «-тип» пришёл из теории типов, с помощью которой можно описать типизацию в языка программирования. Если тип может содержать или значения типа A или значения типа B, то множество его возможных состояний есть теоретико-множественная сумма всевозможных состояний типов A и B. Вам наверняка знакомы «двойники» сигма-типов: типы-произведения, то есть структуры, кортежи и т.п. Например, множество возможных состояний структуры из типов A и B содержит декартово произведение состояний типов A и B.
2. Есть предложение P0095R1 о внесении сопоставления с образцом в Стандарт C++.
Десять возможностей C++11, которые должен использовать каждый C++ разработчик
В данной статье рассматривается ряд возможностей С++11, которые все разработчики должны знать и использовать. Существует много новых дополнений к языку и стандартной библиотеке, эта статья лишь поверхностно охватывает часть из них. Однако, я полагаю, что некоторые из этих новых функций должны стать обыденными для всех разработчиков С++. Подобных статей наверное существует много, в этой я предприму попытку составить список возможностей, которые должны войти в повседневное использование.
#1 — auto
До С++11, ключевое слово auto использовалось как спецификатор хранения переменной (как, например, register, static, extern ). В С++11 auto позволяет не указывать тип переменной явно, говоря компилятору, чтобы он сам определил фактический тип переменной, на основе типа инициализируемого значения. Это может использоваться при объявлении переменных в различных областях видимости, как, например, пространство имен, блоки, инициализация в цикле и т.п.
#2 — nullptr
#3 — range-based циклы
Это полезно, когда вы просто хотите получить элементы массива/контейнера или сделать с ними что-то, не заботясь об индексах, итераторах или кол-ве элементов.
#4 — override и final
Мне всегда не нравились виртуальные функции в С++. Ключевое слово virtual опционально и поэтому немного затрудняло чтение кода, заставляя вечно возвращаться в вершину иерархии наследования, чтобы посмотреть объявлен ли виртуальным тот или иной метод. Я всегда использовал этой ключевое слово так же и в производных классах (и поощрял людей, кто так делал), чтобы код был понятнее. Тем не менее, есть ошибки, которые могут все таки возникнуть. Возьмем следующий пример:
Вот другая возможная ошибка: параметры одни и те же, но в базовом классе метод константный, а в производном — нет.
Теперь это вызовет ошибку при компиляции (точно так же, если бы вы использовали override во втором примере):
#5 — строго-типизированный enum
У «традиционных» перечислений в С++ есть некоторые недостатки: они экспортируют свои значения в окружающую область видимости (что может привести к конфликту имен), они неявно преобразовываются в целый тип и не могут иметь определенный пользователем тип.
#6 — интеллектуальные указатели
Первое объявление эквивалентно следующему:
make_shared — это функция, имеющая преимущество при выделении памяти для совместно используемого объекта и интеллектуального указателя с единственным выделением, в отличие от явного получения shared_ptr через конструктор, где требуется, по крайней мере, два выделения. Из-за этого может произойти утечка памяти. В следующем примере как раз это демонстрируется, утечка может произойти в случае, если seed() бросит исключение.
#7 — лямбды
#8 — non-member begin() и end()
Давайте возьмем, например, предыдущий пример, где я выводил вектор и затем искал первый нечетный элемент. Если std::vector заменить С-подобным массивом, то код будет выглядеть так:
С begin() и end() его можно переписать следующим образом:
#9 — static_assert и классы свойств
static_assert проверяет утверждение во время компиляции. Если утверждение — истина, то ничего не происходит. Если — ложь, то компилятор выводит указанное сообщение об ошибке.
Однако, при компиляции не возникнет ошибки, если написать следующее:
#10 — семантика перемещения
Это — еще одна важная тема, затронутая в С++11. На эту тему можно написать несколько статей, а не абзацев, поэтому я не буду сильно углубляться.
C++11 ввел понятие rvalue ссылок (указанных с &&), чтобы отличать ссылка на lvalue (объект, у которого есть имя) и rvalue (объект, у которого нет имени). Семантика перемещения позволяет изменять rvalues (ранее они считались неизменными и не отличались от типов const T&).
Класс/структура раньше имели некоторые неявные функции-члены: конструктор по умолчанию (если другой конструктор не определен), конструктор копирования и деструктор. Конструктор копирования выполняет поразрядное копирование переменных. Это означает, что если у вас есть класс с указателями на какие-то объекты, то конструктор копирования скопирует указатели, а не объекты, на которые они указывают. Если вы хотите получить в копии именно объекты, а не лишь указатели на них, вы должны это явно описать в конструкторе копирования.
Конструктор перемещения и оператор присваивания перемещения — эти две специальные функции принимают параметр T&&, который является rvalue. Фактически, они могут изменять объект.
Следующий пример показывает фиктивную реализацию буфера. Буфер идентифицируется именем, имеет указатель (обернутый в std::unique_ptr ) на массив элементов типа Т и переменную, содержащую размер массива.
Конструктор копирования по умолчанию и оператор присваивания копии должны быть вам знакомы. Новое в С++11 — это конструктор перемещения и оператор присваивания перемещения, Если вы выполните этот код, то увидите, что когда создается b4 — вызывается конструктор перемещения. Кроме того, когда b1 присваивается значение — вызывается оператор присваивания перемещения. Причина — значение, возвращаемое функцией getBuffer() — rvalue.
Руководство Google по стилю в C++. Часть 3
Часть 1. Вступление
Часть 2. Заголовочные файлы
Часть 3. Область видимости
Часть 4. Классы
…
Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.
Область видимости
Пространство имён
Размещайте свой код в пространстве имён (за некоторыми исключениями). Пространство имён должно иметь уникальное имя, формируемое на основе названия проекта, и, возможно, пути. Не используйте директиву using (например, using namespace foo). Не используйте встроенные (inline) пространства имён. Для безымянных пространств имён смотрите Безымянные пространства имён и статические переменные.
Определение
Пространства имён делят глобальную область видимости на отдельные именованные области позволяя избежать совпадения (коллизий) имён.
За
Пространства имён позволяют избежать конфликта имён в больших программах, при этом сами имена остаются достаточно короткими.
Например, если два разных проекта содержат класс Foo в глобальной области видимости, имена могут конфликтовать. Если каждый проект размещает код в своё пространство имён, то project1::Foo и project2::Foo будут разными именами, конфликтов не будет, в то же время код каждого проекта будет использовать Foo без префикса.
Пространства имён inline автоматически делают видимыми свои имена для включающего пространства имён. Рассмотрим пример кода:
Здесь выражения outer::inner::foo() и outer::foo() взаимозаменяемы. Inline пространства имён в основном используются для ABI-совместимости разных версий.
Против
Пространства имён могут запутать программиста, усложнить понимание, что к чему относится.
Пространства имён inline, в частности, могут сбивать с толку т.к. область видимости не ограничена местом определения. Поэтому такой вид пространств имён может быть полезен только при обновлении интерфейсов с сохранением совместимости.
В ряде случаев требуется использование полных имён и это может сделать код сильно перегруженным.
Заключайте в пространство имён целиком файл с исходным кодом после #include-ов, объявлений/определений gflag-ов и предварительных объявлений классов из других пространств имён.
В .cc файлах могут быть дополнительные объявления, такие как флаги или using-декларации.
Не используйте using-директиву чтобы сделать доступными все имена из пространства имён.
Не используйте псевдонимы пространств имён в блоке namespace в заголовочном файле, за исключением явно обозначенных «внутренних» пространств имён. Связано это с тем, что любая декларация в заголовочном файле становится частью публичного API, экспортируемого этим файлом.
Безымянные пространства имён и статические переменные
Когда определения внутри .cc файла не используются в других исходных файлах, размещайте такие определения в безымянном пространстве имён или объявляйте их как static. Не используйте такой тип определения в заголовочных файлах (.h).
Определение
Размещённые в безымянном пространстве имён объявления могут быть слинкованы как internal (только для внутреннего использования). Функции и переменные также могут быть с internal линковкой, если они заявлены как static. Такие типы объявления подразумевают, что они будут недоступны из другого файла. Если другой файл объявляет сущность с таким же именем, то оба объявления будут полностью независимы.
Вердикт
Использование internal линковки в .cc файлах предпочтительно для любого кода, к которому не обращаются снаружи (из других файлов). Не используйте подходы internal линковки в .h файлах.
Формат описания безымянного пространства имён полностью аналогичен именованному варианту. Не забывайте к закрывающей скобке написать комментарий, в котором имя оставьте пустым:
Функции: глобальные, статические внутри класса, вне класса
Предпочтительно размещать функции либо внутри класса, либо в некотором пространстве имён. Использование глобальных функций должно быть минимальным. Также не используйте класс для группировки различных функций, объявляя их статическими в одном классе: статические функции (по-правильному) должны использоваться для работы с экземплярами класса или его статическими данными.
За
Вообще функции (как статические, так и вне класса) довольно полезная штука, Кэп. И размещение функций либо в классе, либо в пространстве имён позволяет всё остальное содержать в чистоте (я про глобальное пространство имён).
Против
Иногда разумнее статические функции класса и функции вне класса сгруппировать в одном месте, в новом классе. Например, когда у них сложные зависимости от всего или им нужен доступ к внешним ресурсам.
Вердикт
Иногда полезно объявить функцию, не привязанную к экземпляру класса. И можно сделать либо статическую функцию в классе, либо внешнюю (вне класса) функцию. Желательно, чтобы функция-вне-класса не использовала внешних переменных и находилась в пространстве имён. Не создавайте классы только для группировки статических функций: это всё равно, что дать функциям некий префикс и группировка становится лишней.
Если требуется определить функцию: в .cc-файле; вне класса; используемой только в локальном файле — используйте internal линковку для ограничения области видимости.
Локальные переменные
Объявляйте переменные внутри функции в наиболее узкойобласти видимости, инициализируйте такие переменные при объявлении.
Язык C++ позволяет объявлять переменные в любом месте функции. Однако рекомендуется делать это в наиболее узкой (наиболее вложенной) области видимости, и по возможности ближе к первому использованию. Это облегчает поиск объявлений, проще узнать тип переменной и её начальное значение. Также рекомендуется использовать инициализацию, а не объявление с присваиванием. Примеры:
Переменные, необходимые только внутри кода if, while и for лучше объявлять внутри условий, тогда область их видимости будет ограничена только соответствующим блоком кода:
Однако учитывайте одну тонкость: если переменная есть экземпляр объекта, то при каждом входе в область видимости будет вызываться конструктор, и, соответственно, при выходе будет вызываться деструктор.
Возможно было бы более эффективно такую переменную (которая используется внутри цикла) объявить вне цикла:
Переменные: статические и глобальные
Объекты в статической области видимости/действия запрещены, кроме тривиально удаляемых. Фактически это означает, что деструктор должен ничего не делать (включая вложенные или базовые типы). Формально это можно описать, что тип не содержит пользовательского или виртуального деструктора и что все базовые типы и не-статические члены ведут себя аналогично (т.е. являются тривиально удаляемыми). Статические переменные в функциях могут быть динамически инициализированными. Использование же динамической инициализации для статических членов класса или переменных в области пространства имён (namespace) в целом не рекомендуется, однако допустимо в ряде случаев (см. ниже).
Эмпирическое правило: если глобальную переменную (рассматривая её изолированно) можно объявить как constexpr, значить она соответствует вышеуказанным требованиям.
Определение
Каждый объект имеет тот или иной тип времени жизни / storage duration, и, очевидно, это влияет на время жизни объекта. Объекты статического типа доступны с момента их инициализации до момента завершения программы. Такие объекты могут быть переменными в пространстве имён («глобальные переменные»), статическими членами классов, локальными переменными внутри функций со спецификатором static. Статические переменные в функциях инициализируются, когда поток выполнения кода проходит в первый раз через объявление; все остальные объекты статического типа инициализируются в фазе старта (start-up) приложения. Все объекты статического типа удаляются в фазе завершения программы (до обработки незавершённых(unjoined) потоков).
Инициализация может быть динамическая, т.е. во время инициализации делается что-то нетривиальное: например, конструктор выделяет память, или переменная инициализируется идентификатором процесса. Также инициализации может быть статической. Сначала выполняется статическая инициализация: для всех объектов статического типа (объект инициализируется либо заданной константой, либо заполняется нулями). Далее, если необходимо, выполняется динамическая инициализация.
За
Глобальные и статические переменные бывают очень полезными: константные имена, дополнительные структуры данных, флаги командной строки, логирование, регистрирование, инфраструктура и др.
Против
Глобальные и статические переменные с динамической инициализацией или нетривиальным деструктором могут сильно усложнить код и привести к трудно обнаруживаемым багам. Порядок динамической инициализации (и разрушения) объектов может быть различным когда есть несколько единиц трансляции. И, например, когда одна из инициализаций ссылается на некую переменную статического типа, то возможна ситуация доступа к объекту до корректного начала его жизненного цикла (до полного конструирования), или уже после окончания жизненного цикла. Далее, если программа создаёт несколько потоков, которые не завершаются к моменту выхода из программы, то эти потоки могут пытаться получить доступ к объектам, которые уже разрушены.
Вердикт
Когда деструктор тривиальный, тогда порядок разрушения в принципе не важен. В противном случае есть риск обратиться к объекту после его разрушения. Поэтому, настоятельно рекомендуется использовать только переменные со статическим типом размещения (конечно, если они имеют тривиальный деструктор). Фундаментальные типы (указатели или int), как и массивы из них, являются тривиально разрушаемыми. Переменные с типом constexp также тривиально разрушаемые.
Отметим, что ссылка не есть сам объект, и, следовательно, к ним не применяются ограничения по разрушению объекта. Хотя ограничения на динамическую инициализацию остаются в силе. В частности, внутри функции допустим следующий код static T& t = *new T;.
Тонкости инициализации
Инициализация может быть запутанной: мало того, что конструктору нужно (желательно правильно) отработать, так есть ещё и предварительные вычисления:
Константная инициализация является рекомендуемой для большинства случаев. Константную инициализацию переменных со статическим размещением рекомендуется помечать как constexpr или атрибутом ABSL_CONST_INIT
. Любую переменную вне функции, со статическим размещением и без указанной выше маркировки следует считать динамически инициализируемой (и тщательно проверять на ревью кода).
Например, следующие инициализации могут привести к проблемам:
Динамическая инициализация переменных вне функций не рекомендуется. В общем случае это запрещено, однако, это можно делать если никакой код программы не зависит от порядка инициализации этой переменной среди других: в этом случае изменение порядка инициализации не может что-то поломать. Например:
Динамическая же инициализация статических переменных в функциях (локальных) допустима и является широко распространённой практикой.
Стандартные практики
Потоковые переменные
Потоковые переменные (thread_local), объявленные вне функций должны быть инициализированы константой, вычисляемой во время компиляции. И это должно быть сделано с помощью атрибута ABSL_CONST_INIT
. В целом, для определения данных, специфичных для каждого потока, использование thread_local является наиболее предпочтительным.
Определение
Начиная с C++11 переменные можно объявлять со спецификатором thread_local:
Каждая такая переменная представляется собой коллекцию объектов. Разные потоки работают с разными экземплярами переменной (каждый со своим экземпляром). По поведению переменные thread_local во многом похожи на Переменные со статическим типом размещения. Например, они могут быть объявлены в пространстве имён, внутри функций, как статические члены класса (как обычные члены класса — нельзя).
Инициализация потоковых переменных очень напоминает статические переменные, за исключением что это делается для каждого потока. В том числе это означает, что безопасно объявлять thread_local переменные внутри функции. Однако в целом thread_local переменные подвержены тем же проблемам, что и статические переменные (различный порядок инициализации и т.д.).
Переменная thread_local разрушается вместе с завершением потока, так что здесь нет проблем с порядком разрушения, как у статических переменных.
Вердикт
Переменные thread_local, заявленные внутри функций, можно использовать без ограничений, т.к. у них с безопасностью всё отлично. Отметим, что возможно использовать объявленную внутри функции переменную thread_local и вне функции. Для этого нужна функция доступа к переменной:
Переменные thread_local в классе или пространстве имён необходимо инициализировать константой времени компиляции (т.е. динамическая инициализация недопустима). Для этого необходимо аннотировать эти thread_local переменные с помощью ABSL_CONST_INIT
(или constexpr, но это лучше использовать пореже):
Переменные thread_local должны быть предпочтительным способом определения потоковых данных.
Примечания:
Изображение взято из открытого источника.
Руководство Google по стилю в C++. Часть 10
Часть 1. Вступление
…
Часть 9. Комментарии
Часть 10. Форматирование
Часть 11. Исключения из правил
Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.
Форматирование
Стиль кодирования и форматирования являются вещью произвольной, однако проект намного легче управляется, если все следуют одному стилю. Хотя кто-то может не соглашаться со всеми правилами (или пользоваться тем, чем привыкли), очень важно чтобы все следовали единым правилам, чтобы легко читать и понимать чужой код.
Для корректного форматирования мы создали файл настроек для emacs.
Длина строк
Желательно ограничивать длину строк кода 80-ю символами.
Это правило немного спорное, однако масса уже существющего кода придерживается этого принципа, и мы также поддерживаем его.
За
Приверженцы правила утверждают, что строки длиннее не нужны, а постоянно подгонять размеры окон утомительно. Кроме того, некоторые размещают окна с кодом рядом друг с другом и не могут произвольно увеличивать ширину окон. При этом ширина в 80 символов — исторический стандарт, зачем его менять.
Другая сторона утверждает, что длинные строки могут улучшить читабельность кода. 80 символов — пережиток мейнфреймов 1960-х. Современные экраны вполне могут показывать более длинные строки.
80 символов — максимум.
Строка может превышать предел в 80 символов если:
Не-ASCII символы
Не-ASCII символы следует использоваться как можно реже, кодировка должна быть UTF-8.
Вы не должны хардкодить строки для показа пользователю (даже английские), поэтому Не-ASCII символы должны быть редкостью. Однако, в ряде случаев допустимо включать такие слова в код. Например, если код парсит файлы данных (с неанглийской кодировкой), возможно включать в код национальные слова-разделители. В более общем случае, код юнит-тестов может содержать национальные строки. В этих случаях следует использовать кодировку UTF-8, т.к. она понятна большинству утилит (которые понимают не только ASCII).
Кодировка hex также допустима, особенно если она улучшает читабельность. Например, «\xEF\xBB\xBF» или u8″\uFEFF» — неразрывный пробел нулевой длины в Юникоде, и который не должен отображаться в правильном UTF-8 тексте.
Используйте префикс u8 чтобы литералы вида \uXXXX кодировались в UTF-8. Не используйте его для строк, содержащих не-ASCII символы уже закодированные в UTF-8 — можете получить корявый текст если компилятор не распознает исходный код как UTF-8.
Избегайте использования символов C++11 char16_t и char32_t т.к. они нужны для не-UTF-8 строк. По тем же причинам не используйте wchar_t (кроме случаев работы с Windows API, использующий wchar_t).
Пробелы против Табуляции
Используйте только пробелы для отступов. 2 пробела на один отступ.
Мы используем пробелы для отступов. Не используйте табуляцию в своём коде — настройте свой редактор на вставку пробелов при нажатии клавиши Tab.
Объявления и определения функций
Старайтесь размещать тип возвращаемого значения, имя функции и её параметры на одной строке (если всё умещается). Разбейте слишком длинный список параметров на строки также как аргументы в вызове функции.
Пример правильного оформления функции:
В случае если одной строки мало:
или, если первый параметр также не помещается:
Неиспользуемый параметры с неочевидным контекстом следует закомментировать в определении функции:
Атрибуты и макросы старайтесь использовать в начале обьявления или определения функции,
до типа возвращаемого значения:
Лямбды
Форматируйте параметры и тело выражения аналогично обычной функции, список захватываемых переменных — как обычный список.
Для захвата переменных по ссылке не ставьте пробел между амперсандом (&) и именем переменной.
Короткие лямбды можно использовать напрямую как аргумент функции.
Числа с плавающей запятой
Числа с плавающей запятой всегда должны быть с десятичной точкой и числами по обе стороны от неё (даже в случае экспоненциальной нотации). Такой подход улучшить читабельность: все числа с плавающей запятой будут в одинаковом формате, не спутаешь с целым числом, и символы E e экспоненциальной нотации не примешь за шестнадцатеричные цифры. Помните, что число в экспоненциальной нотации не является целым числом.
Вызов функции
Следует либо писать весь вызов функции одной строкой, либо размещать аргументы на новой строке. И отступ может быть либо по первому аргументу, либо 4 пробела. Старайтесь минимизировать количество строк, размещайте по несколько аргументов на каждой строке.
Формат вызова функции:
Если аргументы не помещаются в одной строке, то разделяем их на несколько строк и каждая следующая строка выравнивается на первый аргумент. Не добавляйте пробелы между круглыми скобками и аргументами:
Допускается размещать аргументы на нескольких строках с отступом в 4 пробела:
Старайтесь размещать по несколько аргументов в строке, уменьшая количество строк на вызов функции (если это не ухудшает читабельность). Некоторые считают, что форматирование строго по одному аргументу в строке более читабельно и облегчает редактирование аргументов. Однако, мы ориентируемся прежде всего на читателей кода (не редактирование), поэтому предлагаем ряд подходов для улучшения читабельность.
Если несколько аргементов в одной строке ухудшают читабельность (из-за сложности или запутанности выражений), попробуйте создать для аргументов «говорящие» переменные:
Или разместите сложный аргумент на отдельной строке и добавьте поясняющий комментарий:
Если в вызове функции ещё есть аргументы, которые желательно разместить на отдельной строке — размещайте. Решение должно основываться улучшении читабельность кода.
Иногда аргументы формируют структуру. В этом случае форматируйте аргументы согласно требуемой структуре:
Форматирование списка инициализации
Форматируйте список инициализации аналогично вызову функции.
Если список в скобках следует за именем (например, имя типа или переменной), форматируйте <> как будто это вызов функции с этим именем. Даже если имени нет, считайте что оно есть, только пустое.
Условия
Старайтесь не вставлять пробелы с внутренней стороны скобок. Размещайте if и else на разных строках.
Есть два подхода к форматированию условий. Один допускает пробелы между скобками и условием, другой — нет.
Предпочтительный вариант без пробелов. Другой вариант также допустим, но будьте последовательны. Если вы модифицируйте существующий код — используйте формат, который уже есть в коде. Если вы пишете новый код — используйте формат как у файлов, находящихся в той же директории или используйте формат проекта. Если не уверены — не добавляйте пробелы.
Если используется формат с пробелами:
Заметьте, что в любом случае должен быть пробел между if и открывающей скобкой. Также нужен пробел между закрывающей скобкой и фигурной скобкой (если она есть).
Короткие условия можно записать в одну строку, если это улучшит читабельность. Используйте этот вариант только если строка короткая и условие не содержит секцию else.
Не используйте сокращённый вариант, если есть секция else:
Обычно фигурные скобки не требуются для короткого условия, однако они допустимы. Также сложные условия или код лучше читаются при наличии фигурных скобок. Часто требуют, чтобы любой if был со скобками.
И если одна часть условия использует фигурные скобки, вторую также оформляйте с ними:
Циклы и switch-и
Конструкция switch может использовать скобки для блоков. Описывайте нетривиальные переходы между вариантами. Скобки необязательны для циклов с одним выражением. Пустой цикл должен использовать либо пустое тело в скобках или continue.
Блоки case в switch могут как быть с фигурными скобками, так быть и без них (на ваш выбор). Если же скобки используются, используйте формат, описанный ниже.
Рекомендуется в switch делать секцию default. Это необязательно в случае использования перечисления, да и компилятор может выдать предупреждение если обработаны не все значения. Если секция default не должна выполняться, тогда формируйте это как ошибку. Например:
Переход с одной метки на следующую должен быть помечен макросом ABSL_FALLTHROUGH_INTENDED; (определён в absl/base/macros.h).
Размещайте ABSL_FALLTHROUGH_INTENDED; в точке, где будет переход. Исключение из этого правила — последовательные метки без кода, в этом случае помечать ничего не нужно.
Скобки являются опциональными для циклов с одной операцией.
Пустой цикл должен быть оформлен либо как пара скобок, либо как continue без скобок. Не используйте одиночную точку с запятой.
Указатели и ссылки
Вокруг ‘.’ и ‘->’ не ставьте пробелы. Оператор разыменования или взятия адреса должен быть без пробелов.
Ниже приведены примеры правильного форматирования выражений с указателями и ссылками:
Старайтесь использовать единый стиль в файле кода, при модификации существующего файла применяйте используемое форматирование.
Допускается объявлять несколько переменных одним выражением. Однако не используйте множественное объявление с указателями или ссылками — это может быть неправильно понято.
Логические выражения
Если логическое выражение очень длинное (превышает типовое значение), используйте единый подход к разбивке выражения на строки.
Например, здесь при переносе оператор AND располагается в конце строки:
Отметим, что разбиение кода (согласно примеру) производится так, чтобы && и оператор AND завершали строку. Такой стиль чаще используется с коде Google, хотя расположение операторов в начале строки тоже допустимо. Также, можете добавлять дополнительные скобки для улучшения читабельности. Учтите, что использование операторов в виде пунктуации (такие как && и
) более предпочтительно, что использование операторов в виде слов and и compl.
Возвращаемые значения
Не заключайте простые выражения return в скобки.
Используйте скобки в return expr; только если бы вы использовали их в выражении вида x = expr;.
Инициализация переменных и массивов
Что использовать: =, () или
<> — это ваш выбор.
Вы можете выбирать между вариантами =,
() и <>. Следующие примеры кода корректны:
Будьте внимательны при использовании списка инициализации для типа, у которого есть конструктор с std::initializer_list.
Компилятор предпочтёт использовать конструктор std::initializer_list при наличии списка в фигурных скобках. Заметьте, что пустые фигурные скобки <> — это особый случай и будет вызван конструктор по-умолчанию (если он доступен). Для явного использования конструктора без std::initializer_list применяйте круглые скобки вместо фигурных.
Также конструирование с фигурными скобками запрещает ряд преобразований целых типов (преобразования с уменьшением точности). И можно получить ошибки компиляции.
Директивы препроцессора
Знак # (признак директивы препроцессора) должен быть в начале строки.
Даже если директива препроцессора относится к вложенному коду, директивы пишутся с начала строки.
Форматирование классов
Размещайте секции в следующем порядке: public, protected и private. Отступ — один пробел.
Ниже описан базовый формат для класса (за исключением комментариев, см. описание Комментирование класса):
Списки инициализации конструктора
Списки инициализации конструктора могут быть как в одну строку, так и на нескольких строках с 4-х пробельным отступом.
Ниже представлены правильные форматы для списков инициализации:
Форматирование пространств имён
Содержимое в пространстве имён пишется без отступа.
Пространство имён не добавляет отступов. Например:
Не делайте отступов в пространстве имён:
При объявлении вложенных пространств имён, размещайте каждое объявление на отдельной строке.
Горизонтальная разбивка
Используйте горизонтальную разбивку в зависимости от ситуации. Никогда не добавляйте пробелы в конец строки.
Общие принципы
Добавление разделительных пробелов может мешать при слиянии кода. Поэтому: Не добавляйте разделительных пробелов в существующий код. Вы можете удалить пробелы, если уже модифицировали эту строку. Или сделайте это отдельной операцией (предпочтительно, чтобы с этим кодом при этом никто не работал).
Циклы и условия
Операторы
Шаблоны и приведение типов
Вертикальная разбивка
Сведите к минимуму вертикальное разбиение.
Это больше принцип, нежели правило: не добавляйте пустых строк без особой надобности. В частности, ставьте не больше 1-2 пустых строк между функциями, не начинайте функцию с пустой строки, не заканчивайте функцию пустой строкой, и старайтесь поменьше использовать пустые строки. Пустая строка в блоке кода должна работать как параграф в романе: визуально разделять две идеи.
Базовый принцип: чем больше кода поместится на одном экране, тем легче его понять и отследить последовательность выполнения. Используйте пустую строку исключительно с целью визуально разделить эту последовательность.
Несколько полезных замечаний о пустых строках: