CodeStyle » History » Version 1
Redmine Admin, 03/23/2024 12:46 PM
| 1 | 1 | Redmine Admin | # Стиль кодирования на C++ |
|---|---|---|---|
| 2 | |||
| 3 | {{toc}} |
||
| 4 | |||
| 5 | Данный стиль кодирования основан на |
||
| 6 | Google C++ Style Guide(attachment:cppguide.pdf). В отличие от Google |
||
| 7 | мы не испытываем страха перед исключениями, помимо этого, у нас свои соглашения |
||
| 8 | об именовании переменных, констант и функций. На ~80%, текст соответствует |
||
| 9 | переводу с хабра. |
||
| 10 | |||
| 11 | ## Введение |
||
| 12 | |||
| 13 | Цель руководства — управление сложностью кода, путем описания в деталях как |
||
| 14 | стоит (или не стоит) писать код на C++. Правила этого руководства упростят |
||
| 15 | управление кодом и увеличат продуктивность программистов. |
||
| 16 | |||
| 17 | *Стиль кодирования (codestyle)* — соглашения, которым следует C++ код. Стиль — |
||
| 18 | это больше, чем форматирование файла с кодом. |
||
| 19 | |||
| 20 | > *Примечание:* это руководство не является учебником по C++: предполагается, |
||
| 21 | > что вы знакомы с языком. |
||
| 22 | |||
| 23 | ### Цели Руководства по стилю |
||
| 24 | |||
| 25 | Зачем нужен этот документ? |
||
| 26 | |||
| 27 | Есть несколько основных целей этого документа, лежащих в основе отдельных |
||
| 28 | правил. Используя эти цели можно избежать длинных дискуссий: почему правила |
||
| 29 | такие и зачем им следовать. Если вы понимаете цели каждого правила, то вам легче |
||
| 30 | с ними согласиться или отвергнуть, оценить альтернативы при изменении правил |
||
| 31 | под себя. |
||
| 32 | |||
| 33 | Цели руководства следующие: |
||
| 34 | |||
| 35 | * Правила должны стоить изменений |
||
| 36 | * Преимущества от использования единого стиля должны перевешивать |
||
| 37 | недовольство инженеров по запоминанию и использованию правил. |
||
| 38 | * Преимущество оценивается по сравнению с кодовой базой без применения |
||
| 39 | правил, поэтому если ваши люди всё равно не будут применять правила, то |
||
| 40 | выгода будет очень небольшой. |
||
| 41 | * Этот принцип объясняет почему некоторые правила отсутствуют: например, |
||
| 42 | `goto` нарушает многие принципы, однако он практически не используется, |
||
| 43 | поэтому Руководство это не описывает. |
||
| 44 | * Удобство для читателя, а не для писателя |
||
| 45 | * Кодовая база (и большинство отдельных компонентов из неё) будет |
||
| 46 | использоваться продолжительное время. Поэтому, на чтение этого кода будет |
||
| 47 | тратиться существенно больше времени, чем на написание. |
||
| 48 | * Код должен легко читаться, поддерживаться и отлаживаться. Как следствие, |
||
| 49 | принцип *оставь подсказку для читателя, когда код работает странно*. Если код |
||
| 50 | работает неочевидно, следует использовать такие конструкции, которые явным |
||
| 51 | образом укажут, как именно он работает. Например, использование |
||
| 52 | `std::unique_ptr` **явно** показывает передачу владения. |
||
| 53 | * Кадры приходят и уходят, а хорошо читаемый код позволяет новым специалистам |
||
| 54 | быстрее вникать в работу |
||
| 55 | * Согласование с существующим кодом |
||
| 56 | * Использование единого стиля на кодовой базе позволяет переключиться на |
||
| 57 | другие, более важные, вопросы. |
||
| 58 | * Согласованность позволяет использовать автоматическое форматирование кода. |
||
| 59 | Если код всегда написан похожим образом, то и автоформатирование будет |
||
| 60 | давать похожий результат. |
||
| 61 | * Во многих случаях *согласованность* вырождается до: *выбери из нескольких* |
||
| 62 | *вариантов самый подходящий*, а некоторая гибкость в использовании правил |
||
| 63 | позволяет людям меньше спорить. |
||
| 64 | * По возможности, код должен быть согласован с кодом максимально широкого |
||
| 65 | C++-сообщества |
||
| 66 | * Согласованность кода с кодом других организаций и сообществ весьма полезна. |
||
| 67 | Если возможности стандартного C++ или принятые идиомы языка облегчают |
||
| 68 | написание программ, это повод их использовать. Однако иногда особенности |
||
| 69 | стандарта и идиомы имеют изъяны или неприменимы для внедрения в текущую |
||
| 70 | кодовую базу. В этих случаях (как описано ниже) имеет смысл ограничить или |
||
| 71 | запретить использование некоторых стандартных возможностей. В некоторых |
||
| 72 | случаях создаётся свое решение, которое работает поверх стандартных |
||
| 73 | библиотек, иногда это слишком затратно. |
||
| 74 | * Минимизация неочевидных или опасных конструкций |
||
| 75 | * Некоторые особенности языка С++ более неочевидны, чем может показаться на |
||
| 76 | первый взгляд. Стоит избегать конструкций, если их использование несет больше |
||
| 77 | рисков, чем пользы. |
||
| 78 | * Минимизация конструкций, сложных или трудно поддерживаемых для |
||
| 79 | среднестатистического программиста на C++ |
||
| 80 | * В C++ есть возможности, которые в целом не приветствуются по причине |
||
| 81 | усложнения кода. В часто используемом коде применение хитрых конструкций |
||
| 82 | оправданно, поскольку однократные затраты на понимание сложных вещей будут |
||
| 83 | оправданы преимуществами от более сложной реализации. |
||
| 84 | * Код переходит из рук в руки, а специалисты меняются, поэтому ситуация, |
||
| 85 | когда все в команде понимают какой-то сложный фрагмент кода может поменяться |
||
| 86 | со временем. |
||
| 87 | * Масштаб кода должен учитываться |
||
| 88 | * С кодовой базой более 100 миллионов строк и тысячами инженеров, ошибки и |
||
| 89 | упрощения могут дорого обойтись. Например, важно избегать замусоривания |
||
| 90 | глобального пространства имён: коллизии имён очень сложно избежать в большой |
||
| 91 | кодовой базе если всё объявляется в глобальном пространстве имён. |
||
| 92 | * Использование оптимизации по мере необходимости |
||
| 93 | * Оптимизация производительности иногда важнее, чем следование правилам в |
||
| 94 | кодировании. |
||
| 95 | |||
| 96 | Намерение этого документа — обеспечить максимально понятное руководство при |
||
| 97 | разумных ограничениях. Как всегда, здравый смысл всегда превалирует над |
||
| 98 | документом. Относитесь со скепсисом к хитрым или необычным конструкциям: |
||
| 99 | отсутствие ограничения не всегда есть разрешение. И, если не можешь решить сам, |
||
| 100 | спроси начальника. |
||
| 101 | |||
| 102 | ### Версия C++ |
||
| 103 | |||
| 104 | Сейчас код должен соответствовать C++17, т.е. возможности C++2x нежелательны. |
||
| 105 | В дальнейшем, руководство будет корректироваться на более новые версии C++. |
||
| 106 | Не используйте нестандартные расширения. Учитывайте совместимость с другим |
||
| 107 | окружением, если собираетесь использовать C++14 и C++17 в своём проекте. |
||
| 108 | |||
| 109 | ## Заголовочные файлы |
||
| 110 | |||
| 111 | В общем случае, каждый `.cpp`-файл должен иметь парный `.hpp` файл. Есть |
||
| 112 | исключения, например, модульные тесты или небольшие файлы, содержащие только |
||
| 113 | функцию `main()`. |
||
| 114 | |||
| 115 | Корректное использование заголовочных файлов может иметь огромное влияние на |
||
| 116 | читаемость, размер и производительность кода. |
||
| 117 | |||
| 118 | Нижеописанные правила помогут вам избежать различных трудностей при |
||
| 119 | использовании заголовочных файлов. |
||
| 120 | |||
| 121 | ### Самодостаточные заголовочные файлы |
||
| 122 | |||
| 123 | Заголовочные файлы должны быть *самодостаточными* и иметь расширение `.hpp`. |
||
| 124 | Незаголовочные файлы, предназначенные для включения в код, должны быть с |
||
| 125 | расширением `.inc` и использоваться осторожно. |
||
| 126 | |||
| 127 | *Самодостаточность (self-containment)* заголовочного файла означает, что |
||
| 128 | пользователи или утилиты для рефакторинга должны включать заголовочный файл без |
||
| 129 | каких-либо дополнительных условий. Например, файл `a.hpp` не должен требовать, |
||
| 130 | чтобы в файл с исходным кодом также был включен файл `b.cpp`. |
||
| 131 | Заголовочный файл должен иметь защиту от повторного включения и включать все |
||
| 132 | необходимые файлы. |
||
| 133 | |||
| 134 | При объявлении в заголовочном файле inline-функций или шаблонов, которые будут |
||
| 135 | использоваться пользователем данного файла, определения этих функций или |
||
| 136 | шаблонов должны присутствовать либо в самом заголовочном файле, либо в файле, |
||
| 137 | включенном непосредственно в него. Не следует переносить определения в отдельный |
||
| 138 | заголовочный файл (`inl.hpp`). Так раньше было принято, но теперь это |
||
| 139 | нежелательно. Если все специализации шаблона явно определены в `.cpp`-файле или |
||
| 140 | если шаблон используется только в нем, определение шаблона можно оставить в этом |
||
| 141 | файле. |
||
| 142 | |||
| 143 | В редких случаях включаемые файлы не являются самодостаточными и включаются в |
||
| 144 | середину другого файла. Такие файлы могут не иметь защиты от повторного |
||
| 145 | включения и не включать заголовочные файлы с зависимостями. Такие файлы должны |
||
| 146 | иметь расширение `.inc` и использоваться редко. Использование самодостаточных |
||
| 147 | заголовочных файлов остается предпочтительным. |
||
| 148 | |||
| 149 | ### Защита от повторного включения |
||
| 150 | |||
| 151 | Предпочтительнее использовать директиву `#pragma once`, чем пару |
||
| 152 | `#ifndef/#define/#endif`. Это связано с тем, что последний нарушает принцип DRY, |
||
| 153 | поскольку название заголовочного файла фигурирует в нескольких местах и при |
||
| 154 | рефакторинге приходится менять и сами макросы. |
||
| 155 | |||
| 156 | В некоторых случаях, например, если возможно включение одного и того же файла |
||
| 157 | из разных мест, `#pragma once` может не работать. В подобных ситуациях или |
||
| 158 | где-то еще, можно использовать макросы. В этом случае формат макроопределения |
||
| 159 | иметь следующий вид:`<PROJECT>_<PATH>_<FILE>_H_`. Например, файл |
||
| 160 | `foo/src/bar/baz.h` в проекте foo может иметь следующую блокировку: |
||
| 161 | |||
| 162 | ```c++ |
||
| 163 | #ifndef FOO_BAR_BAZ_H_ |
||
| 164 | #define FOO_BAR_BAZ_H_ |
||
| 165 | ... |
||
| 166 | #endif // FOO_BAR_BAZ_H_ |
||
| 167 | ``` |
||
| 168 | |||
| 169 | ### Включайте только то, что используете |
||
| 170 | |||
| 171 | Если файл ссылается на сущность, определенную в другом месте, этот файл должен |
||
| 172 | напрямую включать заголовочный файл, который предоставляет объявление или |
||
| 173 | определение данной сущности. Заголовочный файл не должен включаться по |
||
| 174 | какой-либо другой причине. |
||
| 175 | |||
| 176 | Не стоит рассчитывать на то, что данный заголовочный файл подключается |
||
| 177 | опосредованно, через какой-то другой. В процессе работы над тем файлом, |
||
| 178 | ненужное `#include`-выражение может быть убрано. Таким образом, файл `foo.cpp` |
||
| 179 | должен напрямую включать `bar.hpp`, при использовании сущностей, объявленных |
||
| 180 | там, даже если файл `foo.hpp` также его включает. Это один из аспектов |
||
| 181 | самодостаточности. |
||
| 182 | |||
| 183 | ### Предварительное объявление |
||
| 184 | |||
| 185 | Избегайте использования предварительных объявлений в `.cpp`-файлах, если это |
||
| 186 | возможно. Вместо этого включайте нужные файлы. |
||
| 187 | |||
| 188 | *Предварительное объявление (forward declaration)* - это объявление сущности без |
||
| 189 | определения, например: |
||
| 190 | |||
| 191 | ```c++ |
||
| 192 | // In a C++ source file: |
||
| 193 | class B; |
||
| 194 | void FuncInB(); |
||
| 195 | extern int variable_in_b; |
||
| 196 | ABSL_DECLARE_FLAG(flag_in_b); |
||
| 197 | ``` |
||
| 198 | |||
| 199 | * Предварительное объявление может уменьшить время компиляции. Использование |
||
| 200 | `#include` требует от компилятора сразу открывать и обрабатывать больше файлов. |
||
| 201 | * Предварительное объявление позволит избежать ненужной перекомпиляции. |
||
| 202 | Применение `#include` может привести к частой перекомпиляции из-за различных |
||
| 203 | изменений в заголовочных файлах. |
||
| 204 | |||
| 205 | --- |
||
| 206 | |||
| 207 | * Предварительное объявление может скрывать зависимости от перекомпиляции при |
||
| 208 | изменении заголовочных файлов. |
||
| 209 | * Предварительное объявление хуже обрабатывается специальными утилитами. |
||
| 210 | * При изменении API, предварительное объявление может стать некорректным. |
||
| 211 | Как результат, предварительное объявление функции или шаблонов может |
||
| 212 | блокировать изменение API: замена типов параметров на похожий, добавление |
||
| 213 | параметров по умолчанию в шаблон, перенос в новое пространство имён. |
||
| 214 | * Предварительное объявление символов из std:: может вызвать неопределённое |
||
| 215 | поведение. |
||
| 216 | * Иногда тяжело понять, что лучше подходит: предварительное объявление или |
||
| 217 | обычный `#include`. Однако, замена `#include` на предварительное объявление |
||
| 218 | может (без предупреждений) поменять смысл кода: |
||
| 219 | |||
| 220 | ```c++ |
||
| 221 | // b.hpp: |
||
| 222 | struct B {}; |
||
| 223 | struct D : B {}; |
||
| 224 | ``` |
||
| 225 | |||
| 226 | ``` c++ |
||
| 227 | // good_user.cpp: |
||
| 228 | #include "b.hpp" |
||
| 229 | |||
| 230 | void f(B*); |
||
| 231 | void f(void*); |
||
| 232 | void test(D* x) { f(x); } // calls f(B*) |
||
| 233 | ``` |
||
| 234 | |||
| 235 | Если в коде заменить `#include` на предварительное объявление для структур |
||
| 236 | `B` и `D`, то `test()` будет вызывать `f(void*)`. |
||
| 237 | * Предварительное объявление множества сущностей может быть чересчур объёмным, |
||
| 238 | и может быть проще подключить заголовочный файл. |
||
| 239 | * Структура кода, допускающая предварительное объявление |
||
| 240 | (и, далее, использование указателей в качестве членов класса) может сделать код |
||
| 241 | запутанным и медленным. |
||
| 242 | |||
| 243 | --- |
||
| 244 | |||
| 245 | _Вывод:_ |
||
| 246 | При всем этом, использование предварительного объявления классов в заголовочном |
||
| 247 | файле практически лишено данных минусов. Предварительное объявление шаблонных |
||
| 248 | классов выглядит громоздким и его следует избегать. В `.cpp`-файлах следует |
||
| 249 | избегать предварительных определений. |
||
| 250 | |||
| 251 | ### Inline-функции |
||
| 252 | |||
| 253 | Inline-функции (встраиваемые функции) - это функции, тело которой |
||
| 254 | подставляется непосредственно в код при каждом вызове. |
||
| 255 | |||
| 256 | Определяйте функции как встраиваемые только когда они маленькие, например не |
||
| 257 | более 10 строк. |
||
| 258 | |||
| 259 | Встраивание функций может генерировать более эффективный код, особенно когда |
||
| 260 | функции очень маленькие. Такие функции удобно использовать для get/set-функции |
||
| 261 | или коротких, но критичных для производительности функций. |
||
| 262 | |||
| 263 | Чрезмерное использование inline-функций может существенно замедлить программу. |
||
| 264 | В зависимости от размера, их использование может как увеличить, так и уменьшить |
||
| 265 | размер кода. На современных процессорах более компактный код выполняется быстрее |
||
| 266 | благодаря лучшему использованию кэша инструкций. |
||
| 267 | |||
| 268 | _Вывод:_ |
||
| 269 | |||
| 270 | *Хорошее правило*: не объявлять функции встраиваемыми, если они длиннее 10 |
||
| 271 | строк. Осторожно с деструкторами! Деструктор неявно вызывает деструкторы |
||
| 272 | родительских классов. |
||
| 273 | *Еще одно правило*: нет смысла делать функции встраиваемыми, если в них есть |
||
| 274 | `switch` или цикл. |
||
| 275 | |||
| 276 | Важно понимать, что не всегда директива `inline` делает функцию встраиваемой, |
||
| 277 | это зависит от компилятора. Например, виртуальные методы и рекурсивные функции |
||
| 278 | небольшой размер, например get/set-функции. |
||
| 279 | |||
| 280 | ### Названия и порядок включения заголовочных файлов |
||
| 281 | |||
| 282 | Включение заголовочных файлов должно производиться в следующем порядке: |
||
| 283 | Связанный заголовочный файл, Системные заголовочные файлы на языке C, |
||
| 284 | Файлы стандартной библиотеки, Другие библиотеки, Заголовочные файлы текущего |
||
| 285 | проекта. |
||
| 286 | |||
| 287 | Все заголовочные файлы проекта должны указываться относительно директории |
||
| 288 | исходных файлов проекта без использования таких UNIX псевдонимов как . |
||
| 289 | (текущая директория) или .. (родительская директория). Например, |
||
| 290 | `awesome-project/src/base/logging.hpp` должен включаться так: |
||
| 291 | |||
| 292 | ``` c++ |
||
| 293 | #include "base/logging.hpp" |
||
| 294 | ``` |
||
| 295 | |||
| 296 | Другой пример: если основная функция файлов `dir/foo.cpp` и `dir/foo_test.cpp` |
||
| 297 | это реализация и тестирование кода, объявленного в `dir2/foo2.hpp`, |
||
| 298 | то записывайте заголовочные файлы в следующем порядке: |
||
| 299 | |||
| 300 | 1. `dir2/foo2.h`. |
||
| 301 | 2. Пустая строка. |
||
| 302 | 3. Системные заголовочные файлы C (точнее: файлы с включением угловыми скобками |
||
| 303 | с расширением `.h`), например `<unistd.h>`, `<stdlib.h>`. |
||
| 304 | 4. Пустая строка. |
||
| 305 | 5. Заголовочные файлы стандартной библиотеки C++ (без расширения в файлах), |
||
| 306 | например `<algorithm>`, `<cstddef>`. |
||
| 307 | 6. Пустая строка. |
||
| 308 | 7. Заголовочные `.h/.hpp` файлы других библиотек. |
||
| 309 | 8. Пустая строка. |
||
| 310 | 9. Файлы `.hpp` вашего проекта. |
||
| 311 | |||
| 312 | Отделяйте каждую непустую группу файлов пустой строкой. |
||
| 313 | |||
| 314 | Такой порядок файлов позволяет выявить ошибки, когда в парном заголовочном файле |
||
| 315 | (`dir2/foo2.hpp`) пропущены необходимые заголовочные файлы (системные и др.) и |
||
| 316 | сборка соответствующих файлов `dir/foo.cpp` или `dir/foo_test.cc` |
||
| 317 | завершится ошибкой. Как результат, ошибка сразу же появится у разработчика, |
||
| 318 | работающего с этими файлами (а не у другой команды, которая только использует |
||
| 319 | внешнюю библиотеку). |
||
| 320 | |||
| 321 | Учтите, что заголовочные файлы C, такие как `stddef.h` обычно взаимозаменяемы |
||
| 322 | соответствующими файлами C++ (`cstddef`). Можно использовать любой вариант, |
||
| 323 | но лучше следовать стилю существующего кода. |
||
| 324 | |||
| 325 | Внутри каждой секции заголовочные файлы следует перечислять в |
||
| 326 | алфавитном порядке. Учтите, что ранее написанный код может не следовать этому |
||
| 327 | правилу. По возможности (например, при исправлениях в файле), исправляйте |
||
| 328 | порядок файлов на правильный, если это удобно. |
||
| 329 | |||
| 330 | Например, список заголовочных файлов в |
||
| 331 | `awesome-project/src/foo/internal/fooserver.cpp` может выглядеть так: |
||
| 332 | |||
| 333 | ``` c++ |
||
| 334 | #include "foo/server/fooserver.hpp" |
||
| 335 | |||
| 336 | #include <sys/types.h> |
||
| 337 | #include <unistd.h> |
||
| 338 | |||
| 339 | #include <string> |
||
| 340 | #include <vector> |
||
| 341 | |||
| 342 | #include "base/basictypes.hpp" |
||
| 343 | #include "base/commandlineflags.hpp" |
||
| 344 | #include "foo/server/bar.hpp" |
||
| 345 | ``` |
||
| 346 | |||
| 347 | #### Исключения |
||
| 348 | |||
| 349 | Бывают случаи, когда требуется включение заголовочных файлов в зависимости от |
||
| 350 | условий препроцессора (например, в зависимости от используемой ОС). Включение системно-зависимых файлов стоит |
||
| 351 | держать локализованным фрагментом после других заголовочных файлов. Например: |
||
| 352 | |||
| 353 | ``` c++ |
||
| 354 | #include "foo/public/fooserver.h" |
||
| 355 | |||
| 356 | #include "base/port.h" |
||
| 357 | |||
| 358 | // For LANG_CXX11. |
||
| 359 | #ifdef LANG_CXX11 |
||
| 360 | #include <initializer_list> |
||
| 361 | #endif // LANG_CXX11 |
||
| 362 | ``` |
||
| 363 | |||
| 364 | ## Область видимости |
||
| 365 | |||
| 366 | ### Пространства имен |
||
| 367 | |||
| 368 | Код, за некоторым исключением должен быть размещен в пространстве имен. |
||
| 369 | Имена пространств имен должны быть уникальными и основываться на названии и, |
||
| 370 | возможно, пути проекта. Не используйте *using-директивы* |
||
| 371 | (`using namespace std`). Не используйте inline пространства имен. Для |
||
| 372 | безымянных пространств имен см. |
||
| 373 | [внутреннее связывание](#Внутреннее-связывание-Internal-Linkage). |
||
| 374 | |||
| 375 | *Пространства имен (namespaces)* разбивают глобальную область видимости на |
||
| 376 | несколько, отдельных и именованных. Это помогает предотвращать коллизии имен в |
||
| 377 | глобальной области видимости. |
||
| 378 | |||
| 379 | Пространства имен позволяют избегать конфликтов названий, при этом, большая |
||
| 380 | часть кода использует короткие названия. |
||
| 381 | |||
| 382 | Например, представим себе, что два различных проекта имеют класс `Foo` в |
||
| 383 | глобальной области видимости. Эти сущности могут столкнуться как во время |
||
| 384 | компиляции, так и во время исполнения. Если код каждый проект будет помещен в |
||
| 385 | свое пространство имен, то теперь `project1::Foo` и `project2::Foo` - это две |
||
| 386 | отдельных сущности, и теперь коллизия не возникнет, при этом внутри проектов |
||
| 387 | обращение к соответствующему классу `Foo` будет осуществляться без каких-либо |
||
| 388 | префиксов. |
||
| 389 | |||
| 390 | Inline-пространства имен автоматически подставляют названия в окружающую область |
||
| 391 | видимости. Например: |
||
| 392 | |||
| 393 | ```c++ |
||
| 394 | namespace outer { |
||
| 395 | inline namespace inner { |
||
| 396 | void foo(); |
||
| 397 | } // namespace inner |
||
| 398 | } // namespace outer |
||
| 399 | ``` |
||
| 400 | |||
| 401 | Выражения `outer::inner::foo()` и `outer::foo()` - взаимозаменяемы. |
||
| 402 | Inline-пространства имён в основном используются для совместимости ABI между |
||
| 403 | версиями. |
||
| 404 | |||
| 405 | Пространства имен могут запутать, поскольку они усложняют понимание того, какое |
||
| 406 | определение к какому названию относится. |
||
| 407 | |||
| 408 | Inline-пространства имен запутывают еще сильнее, поскольку название не |
||
| 409 | ограничены своим пространством имен. Это просто полезная часть политики |
||
| 410 | управления версиями. |
||
| 411 | |||
| 412 | В ряде случаев требуется использование полных имён, что может добавить в код |
||
| 413 | беспорядка. |
||
| 414 | |||
| 415 | Пространства имен следует использовать следующим образом: |
||
| 416 | |||
| 417 | * Следуйте [правилам](#Именование-пространства-имён-namespace) именования |
||
| 418 | пространств имен. |
||
| 419 | * Завершайте пространства имен комментариями, как в примере ниже |
||
| 420 | * Заключайте в пространство имён целиком файл с исходным кодом после |
||
| 421 | `#include`-ов, макросов, объявлений/определений `gflag`-ов и предварительных |
||
| 422 | объявлений классов из других пространств имён. |
||
| 423 | |||
| 424 | ``` c++ |
||
| 425 | // В .hpp файле |
||
| 426 | namespace mynamespace { |
||
| 427 | |||
| 428 | // Все объявления внутри блока пространства имён. |
||
| 429 | // Обратите внимание на отсутствие отступа. |
||
| 430 | class MyClass { |
||
| 431 | public: |
||
| 432 | ... |
||
| 433 | void Foo(); |
||
| 434 | }; |
||
| 435 | } // namespace mynamespace |
||
| 436 | ``` |
||
| 437 | |||
| 438 | ``` c++ |
||
| 439 | // В .cpp файле |
||
| 440 | namespace mynamespace { |
||
| 441 | |||
| 442 | // Определение функций внутри блока пространства имён. |
||
| 443 | void MyClass::Foo() { |
||
| 444 | ... |
||
| 445 | } |
||
| 446 | } // namespace mynamespace |
||
| 447 | ``` |
||
| 448 | * Не объявляйте ничего в пространстве имен `std`. В том числе и предварительные |
||
| 449 | объявления. |
||
| 450 | * Не используйте директиву `using namespace ...;` - это загрязняет глобальное |
||
| 451 | пространство имен. |
||
| 452 | * Не используйте using-объявление `using foo::Bar;` в заголовочных файлах. |
||
| 453 | * Не используйте псевдонимы пространств имен в заголовочных файлах за |
||
| 454 | исключением явно внутренних пространств имен. |
||
| 455 | |||
| 456 | ``` с++ |
||
| 457 | // Укороченная запись для доступа к часто |
||
| 458 | // используемым именам в .cpp файлах - Нормально |
||
| 459 | namespace baz = ::foo::bar::baz; |
||
| 460 | ``` |
||
| 461 | |||
| 462 | ``` c++ |
||
| 463 | // Укороченная запись для доступа к часто используемым именам (в .hpp файле). |
||
| 464 | namespace librarian { |
||
| 465 | namespace impl { // Внутреннее содержимое, не являющееся частью API. |
||
| 466 | namespace sidetable = ::pipeline_diagnostics::sidetable; |
||
| 467 | } // namespace impl |
||
| 468 | |||
| 469 | inline void my_inline_function() { |
||
| 470 | // Пространство имён, локальное для функции (или метода). |
||
| 471 | namespace baz = ::foo::bar::baz; |
||
| 472 | ... |
||
| 473 | } |
||
| 474 | } // namespace librarian |
||
| 475 | ``` |
||
| 476 | * Не используйте inline-пространства имен. |
||
| 477 | |||
| 478 | ### Внутреннее связывание (Internal Linkage) |
||
| 479 | |||
| 480 | В том случае, если возможность ссылки на определения из `.cpp`-файла из других |
||
| 481 | файлов должна быть запрещена, необходимо использовать внутреннее связывание. |
||
| 482 | Для этого нужно либо объявить эти сущности как `static`, либо разместить их в |
||
| 483 | безымянном пространстве имен (unnamed namespace). Не используйте внутреннее |
||
| 484 | связывание в заголовочных файлах. |
||
| 485 | |||
| 486 | Форматирование безымянных пространств производится так же, как и именованных. |
||
| 487 | В завершающем комментарии, название пространства имен должно быть оставлено |
||
| 488 | пустым. |
||
| 489 | |||
| 490 | ``` c++ |
||
| 491 | namespace { |
||
| 492 | ... |
||
| 493 | } // namespace |
||
| 494 | ``` |
||
| 495 | |||
| 496 | ### Функции: глобальные, статические внутри и вне классов |
||
| 497 | |||
| 498 | Глобальные функции предпочтительно располагать в пространствах имен; используйте |
||
| 499 | абсолютно глобальные функции как можно реже. Не используйте классы просто для |
||
| 500 | того, чтобы сгруппировать статические члены. Статические методы класса должны |
||
| 501 | быть сильно связаны с объектами данного класса или его статическими данными. |
||
| 502 | |||
| 503 | Глобальные и статические функции могут быть полезны в ряде случаев. Помещение |
||
| 504 | глобальных функций в пространство имен предотвращает загрязнение глобального |
||
| 505 | пространства имен. |
||
| 506 | |||
| 507 | Глобальные и статические функции могут стать понятнее при помещении в новый |
||
| 508 | класс как функций-членов, особенно если они имеют доступ ко внешним ресурсам |
||
| 509 | или имеют явные зависимости. |
||
| 510 | |||
| 511 | Иногда полезно объявить функцию, не привязанную к объекту класса. Такие функции |
||
| 512 | могут быть глобальными или статическими. Глобальная функция не должна иметь |
||
| 513 | зависимостей от внешних переменных и практически всегда должна быть помещена |
||
| 514 | в пространство имен. Не создавайте класс для группировки таких функций. |
||
| 515 | Это ничем не отличается от обычного префикса и часто в этом нет смысла. |
||
| 516 | |||
| 517 | Если глобальная функция используется только в одном `.cpp`-файле, |
||
| 518 | используйте [внутреннее связывание](#Внутреннее-связывание-Internal-Linkage). |
||
| 519 | |||
| 520 | ### Локальные переменные |
||
| 521 | |||
| 522 | Располагайте локальные переменные функций в максимально узкой области видимости, |
||
| 523 | инициализируйте эти переменные при объявлении. |
||
| 524 | |||
| 525 | Язык C++ допускает объявление переменной в любом месте функции. Рекомендуется |
||
| 526 | делать это в максимально локальной области видимости и максимально близко к |
||
| 527 | месту первого использования. Так читателю будет проще найти объявление |
||
| 528 | переменной и понять, каким значением она инициализирована. В частности |
||
| 529 | инициализация более предпочтительна, чем объявление и присваивание. Например: |
||
| 530 | |||
| 531 | ```c++ |
||
| 532 | int i; |
||
| 533 | i = f(); // Плохо -- инициализация отделена от объявления. |
||
| 534 | |||
| 535 | int j = g(); // Хорошо -- объявление с инициализацией. |
||
| 536 | |||
| 537 | std::vector<int> v; |
||
| 538 | v.push_back(1); // Желательно инициализировать с помощью {}. |
||
| 539 | v.push_back(2); |
||
| 540 | |||
| 541 | std::vector<int> v = {1, 2}; // Хорошо -- v сразу инициализирован. |
||
| 542 | ``` |
||
| 543 | |||
| 544 | Переменные, необходимые для использования внутри конструкций `if`, `while`, |
||
| 545 | `for`, следует объявлять внутри условий цикла. Тогда их область видимости будет |
||
| 546 | ограничена этими циклами: |
||
| 547 | |||
| 548 | ```c++ |
||
| 549 | while (const char* p = strchr(str, '/')) str = p + 1; |
||
| 550 | ``` |
||
| 551 | |||
| 552 | Существует тонкость: Если переменная - это объект, то его конструктор будет |
||
| 553 | вызываться каждый раз при входе в область видимости, а деструктор - при выходе |
||
| 554 | из нее. |
||
| 555 | |||
| 556 | ```c++ |
||
| 557 | // Неэффективная реализация: |
||
| 558 | for (int i = 0; i < 1000000; ++i) { |
||
| 559 | Foo f; // Конструктор и деструктор Foo вызовутся по 1000000 раз каждый. |
||
| 560 | f.DoSomething(i); |
||
| 561 | } |
||
| 562 | ``` |
||
| 563 | |||
| 564 | Объявление такой переменной вне цикла может быть более эффективным: |
||
| 565 | |||
| 566 | ```c++ |
||
| 567 | Foo f; // Конструктор и деструктор Foo вызовутся по одному разу. |
||
| 568 | for (int i = 0; i < 1000000; ++i) { |
||
| 569 | f.DoSomething(i); |
||
| 570 | } |
||
| 571 | ``` |
||
| 572 | |||
| 573 | ### Статические и глобальные переменные |
||
| 574 | |||
| 575 | В статической области видимости (static storage duration) запрещены объекты, |
||
| 576 | за исключением тривиально удаляемых (trivially destructible). Это значит, что |
||
| 577 | деструктор такого объекта не должен делать ничего. Это же относится и к |
||
| 578 | деструкторам базовых классов, поскольку они будут вызваны неявно. |
||
| 579 | Более формально: тип не содержит пользовательского или виртуального деструктора, |
||
| 580 | а также все его члены и базовые типы являются тривиально-удаляемыми. Статические |
||
| 581 | переменные в функциях могут использовать динамическую инициализацию. |
||
| 582 | Динамическая инициализация в статических переменных классов или глобальных |
||
| 583 | переменных не приветствуется, но разрешена в некоторых случаях. Подробнее ниже. |
||
| 584 | |||
| 585 | _Хорошее правило:_ глобальная переменная удовлетворяет данным требованиям, |
||
| 586 | если она может быть объявлена как `constexpr`. |
||
| 587 | |||
| 588 | У каждого объекта есть *время хранения (storage duration)*, которое соотносится |
||
| 589 | с его временем жизни. Объекты со статическим временем хранения живут от точки |
||
| 590 | инициализации и до конца работы программы. Такие объекты встречаются в качестве |
||
| 591 | глобальных переменных, статических переменных классов и статических переменных |
||
| 592 | функций. Все объекты со статическим временем хранения удаляются в фазе |
||
| 593 | завершения программы (до обработки незавершённых потоков). |
||
| 594 | |||
| 595 | Инициализация может быть *динамической*, когда в конструкторе случается что-либо |
||
| 596 | нетривиальное, например выделение памяти, открытие файла, получение системных |
||
| 597 | ресурсов. Другой вид инициализации - *статическая*. Одна инициализация не |
||
| 598 | исключает другой: статическая инициализации выполняется в любом случае, |
||
| 599 | затем, если необходимо, выполняется динамическая инициализация. |
||
| 600 | |||
| 601 | Глобальные и статические переменные очень полезны во многих применениях: |
||
| 602 | именованные константы, внутренние дополнительные структуры данных, |
||
| 603 | флаги командной строки, логирование, механизмы регистрации, организация |
||
| 604 | внутренней инфраструктуры и т. д. |
||
| 605 | |||
| 606 | Глобальные и статические переменные, использующие динамическую инициализацию или |
||
| 607 | нетривиальные деструкторы могут легко привести к трудно обнаруживаемым багам. |
||
| 608 | Порядок динамической инициализации и удаления между единицами трансляции |
||
| 609 | не определен явно. Если один такой объект зависит от другого, то доступ к нему |
||
| 610 | может осуществляться еще до того, как его время жизни началось или уже после |
||
| 611 | того, как оно закончилось. Более того, когда приложение завершает потоки, |
||
| 612 | они могут попытаться обратиться к уже удаленным или удаляемым объектам. |
||
| 613 | |||
| 614 | #### Ограничения по уничтожению |
||
| 615 | |||
| 616 | В случае с тривиальными деструкторами, их исполнение не зависит от порядка, |
||
| 617 | поскольку, никакого "исполнения" деструкторов нет. В противном случае существует |
||
| 618 | риск того, что доступ к объектам будет осуществляться после окончания их времени |
||
| 619 | жизни. По этой причине в статической области видимости разрешены только |
||
| 620 | тривиально-уничтожаемые объекты. Фундаментальные типы данных, такие как |
||
| 621 | указатели или `int` являются тривиально-уничтожаемыми, как и массивы |
||
| 622 | тривиально-уничтожаемых данных. Переменные, отмеченные как `constexpr` являются |
||
| 623 | тривиально-уничтожаемыми. |
||
| 624 | |||
| 625 | ```c++ |
||
| 626 | const int kNum = 10; // Допустимо |
||
| 627 | |||
| 628 | struct X { int n; }; |
||
| 629 | const X kX[] = {{1}, {2}, {3}}; // Допустимо |
||
| 630 | |||
| 631 | void foo() { |
||
| 632 | static const char* const kMessages[] = {"hello", "world"}; // Допустимо |
||
| 633 | } |
||
| 634 | |||
| 635 | // Допустимо: constexpr всегда имеет тривиальный деструктор |
||
| 636 | constexpr std::array<int, 3> kArray = {{1, 2, 3}} |
||
| 637 | ``` |
||
| 638 | |||
| 639 | ```c++ |
||
| 640 | // Плохо: нетривиальный деструктор |
||
| 641 | const std::string kFoo = "foo"; |
||
| 642 | |||
| 643 | // Плохо по тем же причинам (хотя kBar и является ссылкой, но |
||
| 644 | // правило применяется и для временных объектов в расширенным временем жизни) |
||
| 645 | const std::string& kBar = StrCat("a", "b", "c"); |
||
| 646 | |||
| 647 | void bar() { |
||
| 648 | // Плохо: нетривиальный деструктор |
||
| 649 | static std::map<int, int> kData = {{1, 0}, {2, 0}, {3, 0}}; |
||
| 650 | } |
||
| 651 | ``` |
||
| 652 | |||
| 653 | _На заметку_ : ссылки не являются объектами, и на них не распространяются |
||
| 654 | ограничения по уничтожению. Ограничения по инициализации все еще остаются. |
||
| 655 | В частности, статическая ссылка в функции `static T& t = *new T` - разрешена. |
||
| 656 | |||
| 657 | #### Ограничения по инициализации |
||
| 658 | |||
| 659 | Инициализация - это более сложная тема, поскольку необходимо учитывать не только |
||
| 660 | выполнение конструктора класса, но и исполнения инициализации: |
||
| 661 | |||
| 662 | ```c++ |
||
| 663 | int n = 5; // Отлично |
||
| 664 | int m = f(); // ? (Зависит от f) |
||
| 665 | Foo x; // ? (Зависит от Foo::Foo) |
||
| 666 | Bar y = g(); // ? (Зависит от g и Bar::Bar) |
||
| 667 | ``` |
||
| 668 | |||
| 669 | Порядок инициализации для всех выражений, кроме первого, не определен. |
||
| 670 | |||
| 671 | Необходимо использовать концепцию, которая в стандарте C++ называется |
||
| 672 | *константной* *инициализацией (constant initialization)*. Это значит, что |
||
| 673 | выражение инициализации является *константным выражением (constant expression)*, |
||
| 674 | и если объект инициализируется вызовом конструктора, то конструктор должен быть |
||
| 675 | объявлен как `constexpr`: |
||
| 676 | |||
| 677 | ```c++ |
||
| 678 | struct Foo { constexpr Foo(int) {} }; |
||
| 679 | |||
| 680 | int n = 5; // Отлично, 5 - константное выражение |
||
| 681 | |||
| 682 | Foo x(2); // Отлично, 2 - константное выражение |
||
| 683 | // и вызывается constexpr конструктор |
||
| 684 | |||
| 685 | Foo a[] = { Foo(1), Foo(2), Foo(3) }; // Отлично |
||
| 686 | ``` |
||
| 687 | |||
| 688 | Константная инициализация всегда разрешена. Все нелокальные статические |
||
| 689 | переменные, конструкторы которых не помечены как `constexpr` должны |
||
| 690 | восприниматься как динамически инициализируемые и тщательно проверяться. |
||
| 691 | |||
| 692 | Обратите внимание на следующие примеры: |
||
| 693 | |||
| 694 | ```c++ |
||
| 695 | // Объявления, используемые ниже |
||
| 696 | time_t time(time_t*); // не constexpr ! |
||
| 697 | int f(); // не constexpr ! |
||
| 698 | struct Bar { Bar() {} }; |
||
| 699 | |||
| 700 | // Проблемные инициализации |
||
| 701 | time_t m = time(nullptr); // Выражение инициализации не константное |
||
| 702 | Foo y(f()); // Те же проблемы |
||
| 703 | Bar b; // Конструктор Bar::Bar() не является constexpr |
||
| 704 | ``` |
||
| 705 | |||
| 706 | Динамическая инициализация переменных вне функций не рекомендуется. В общем |
||
| 707 | случае это запрещено, однако, это можно делать если остальной код программы не |
||
| 708 | зависит от порядка инициализации этой переменной. В этом случае изменение |
||
| 709 | порядка инициализации не может что-то изменить. |
||
| 710 | Например: |
||
| 711 | |||
| 712 | ```c++ |
||
| 713 | int p = getpid(); // Допустимо, пока другие статические переменные |
||
| 714 | // не используют p в своей инициализации |
||
| 715 | ``` |
||
| 716 | |||
| 717 | Динамическая инициализация статических локальных переменных функций допустима и |
||
| 718 | является широко распространённой практикой. |
||
| 719 | |||
| 720 | #### Рекомендуемые практики |
||
| 721 | * Глобальные строки: Если требуется именованная глобальная или статическая |
||
| 722 | строковая переменная, используйте `constexpr string_view`, массив символов или |
||
| 723 | указатель, указывающий на строковый литерал. |
||
| 724 | * Динамические контейнеры (`map`, `set` и т.д.): если требуется статическая |
||
| 725 | коллекция с фиксированными данными (например, таблицы значений для поиска), то |
||
| 726 | не используйте динамические контейнеры из стандартной библиотеки как тип для |
||
| 727 | статической переменной, т.к. у этих контейнеров нетривиальный деструктор. |
||
| 728 | Вместо этого попробуйте использовать массивы простых (тривиальных) типов, |
||
| 729 | например массив из массивов целых чисел (вместо `std::map<int, int>`) или, |
||
| 730 | например, массив структур с полями `int` и `const char*`. Учтите, что для |
||
| 731 | небольших коллекций линейный поиск обычно вполне приемлем (и может быть очень |
||
| 732 | эффективным благодаря компактному размещению в памяти). Также можете |
||
| 733 | воспользоваться алгоритмами `absl/algorithm/container.h` для стандартных |
||
| 734 | операций. Также возможно создавать коллекцию данных уже отсортированной и |
||
| 735 | использовать алгоритм бинарного поиска. Если без динамического контейнера не |
||
| 736 | обойтись, то попробуйте использовать статическую переменную-указатель, |
||
| 737 | объявленную в функции (см. ниже). |
||
| 738 | * Умные указатели (`unique_ptr`, `shared_ptr`): умные указатели освобождают |
||
| 739 | ресурсы в деструкторе и поэтому их нельзя использовать в качестве статических |
||
| 740 | или глобальных. Попробуйте применить другие практики/способы, описанные в |
||
| 741 | разделе. Например, одно из простых решений это использовать обычный указатель |
||
| 742 | на динамически выделенный объект и далее никогда не удалять его |
||
| 743 | (см. последний вариант списка). |
||
| 744 | * Статические переменные пользовательских типов: если необходима константа |
||
| 745 | типа, определённого вами, определяйте конструктор как `constexpr`, а |
||
| 746 | деструктор - тривиальным. |
||
| 747 | * На крайний случай - создайте статический объект в функции динамически и |
||
| 748 | никогда его не удаляйте. (Например, |
||
| 749 | `static const auto& impl = new T(args...);`). |
||
| 750 | |||
| 751 | ### thread_local-переменные |
||
| 752 | |||
| 753 | `thread_local`-переменные, которые объявлены вне функций должны |
||
| 754 | инициализироваться константами времени компиляции(*true compile-time*). |
||
| 755 | Используйте атрибут `ABSL_CONST_INIT` для того, чтобы в этом убедиться. |
||
| 756 | Использование `thread_local` - это предпочтительный способ объявить данные |
||
| 757 | внутрипотоковыми. |
||
| 758 | |||
| 759 | Начиная с C++11 переменные можно объявлять со спецификатором `thread_local`: |
||
| 760 | |||
| 761 | ``` c++ |
||
| 762 | thread_local Foo foo = ...; |
||
| 763 | ``` |
||
| 764 | |||
| 765 | Каждая такая переменная представляется собой коллекцию объектов. Каждый поток |
||
| 766 | работает со своим экземпляром переменной. По поведению `thread_local`-переменные |
||
| 767 | во многом похожи на |
||
| 768 | [статические переменные](#Статические-и-глобальные-переменные). Например, они |
||
| 769 | могут быть объявлены в пространстве имён, внутри функций, как статические члены |
||
| 770 | класса, но не как обычные члены класса. |
||
| 771 | |||
| 772 | Инициализация потоковых переменных очень напоминает статические переменные, |
||
| 773 | за исключением что это делается для каждого потока. В том числе это означает, |
||
| 774 | что объявлять `thread_local`-переменные внутри функции - безопасно, |
||
| 775 | но остальные `thread_local`-переменные подвержены тем же проблемам, что и |
||
| 776 | статические переменные. |
||
| 777 | |||
| 778 | Переменная `thread_local` не уничтожается до завершения потока, так что здесь |
||
| 779 | нет проблем с порядком разрушения, как у статических переменных. |
||
| 780 | |||
| 781 | * `thread_local`-переменные защищены от гонок, поскольку только один поток |
||
| 782 | имеет к ним доступ, что делает их использование полезным в многопоточных |
||
| 783 | приложениях; |
||
| 784 | * `thread_local`-переменные - это единственный стандартный способ объявить |
||
| 785 | данные внутрипоточными. |
||
| 786 | * Доступ к таким данным может спровоцировать исполнение непредсказуемого и |
||
| 787 | неконтролируемого количества кода. |
||
| 788 | * По сути, `thread_local`-переменные являются глобальными со всеми вытекающими |
||
| 789 | недостатками. |
||
| 790 | * Количество памяти, выделяемое для `thread_local`-переменных зависит от |
||
| 791 | количества запущенных потоков, что может быть чрезмерно большим. |
||
| 792 | * `thread_local`-переменные могут менее эффективны, чем встроенные |
||
| 793 | (*compiler intrinsic*) функции компилятора. |
||
| 794 | |||
| 795 | Размещение `thread_local`-переменных внутри функции не влечет проблем с |
||
| 796 | безопасностью, так что их можно использовать без ограничений. Можно использовать |
||
| 797 | такие переменные для организации глобального или статического доступа к данным, |
||
| 798 | например: |
||
| 799 | |||
| 800 | ```c++ |
||
| 801 | Foo& MyThreadLocalFoo() { |
||
| 802 | thread_local Foo result = ComplicatedInitialization(); |
||
| 803 | return result; |
||
| 804 | } |
||
| 805 | ``` |
||
| 806 | |||
| 807 | В области видимости класса или глобальной `thread_local`-переменных должны |
||
| 808 | инициализироваться константами времени компиляции. Для того, чтобы это |
||
| 809 | гарантировать используйте макрос `ABSL_CONST_INIT` или `constexpr`, но реже. |
||
| 810 | |||
| 811 | ```c++ |
||
| 812 | ABSL_CONST_INIT thread_local Foo foo = ...; |
||
| 813 | ``` |
||
| 814 | |||
| 815 | Используйте `thread_local` для объявления внутрипотоковых данных. |
||
| 816 | |||
| 817 | ## Классы |
||
| 818 | |||
| 819 | Класс - это фундаментальная единица кода на C++. Обычно они используются очень |
||
| 820 | широко. Данный раздел описывает все рекомендации, которым необходимо следовать |
||
| 821 | при разработке классов. |
||
| 822 | |||
| 823 | ### Выполнение задач в конструкторах |
||
| 824 | |||
| 825 | Избегайте вызовов виртуальных методов в конструкторах. Избегайте кода, который |
||
| 826 | может завершиться ошибкой, если не будет способа о ней сообщить. |
||
| 827 | |||
| 828 | В конструкторе возможно выполнить любую инициализацию. |
||
| 829 | |||
| 830 | * Нет необходимости заботиться о том, инициализирован объект класса или нет. |
||
| 831 | * Объекты, которые целиком инициализируются в конструкторе могут быть |
||
| 832 | константами. Также их проще использовать в контейнерах. |
||
| 833 | |||
| 834 | --- |
||
| 835 | |||
| 836 | * Если вызываются виртуальные функции, то не будут вызваны переопределения |
||
| 837 | классов-наследников. |
||
| 838 | * Нет простого способа сообщить об ошибке в конструкторе без аварийного |
||
| 839 | завершения программы. |
||
| 840 | или использования исключений. |
||
| 841 | * Если инициализация провалилась, мы имеем объект в непонятном состоянии |
||
| 842 | * Невозможно взять от конструктора адрес, поэтому возникают сложности с |
||
| 843 | передачей конструктора в другой поток. |
||
| 844 | |||
| 845 | _Вывод:_ |
||
| 846 | Конструкторы никогда не должны вызывать виртуальные функции. Используйте |
||
| 847 | `init`-методы только в случае, если у объекта есть флаги состояния, разрешающие |
||
| 848 | вызывать те или иные публичные функции (т.к. сложно полноценно работать с |
||
| 849 | частично сконструированным объектом). |
||
| 850 | |||
| 851 | ### Неявное преобразование типов |
||
| 852 | |||
| 853 | Не используйте неявное преобразование типов. Используйте ключевое слово |
||
| 854 | `explicit` для операторов преобразования типов или конструкторов с одним |
||
| 855 | аргументом. |
||
| 856 | |||
| 857 | Неявное преобразование разрешает использование объектов одного типа там, |
||
| 858 | где требуется использование объектов другого типа. |
||
| 859 | |||
| 860 | Программист может добавить свои механизмы для неявного преобразования типов |
||
| 861 | путем добавления соответствующих членов в определение класса. Неявное |
||
| 862 | преобразование в исходном типе может быть добавлено с помощью операторов |
||
| 863 | преобразования типов, например `operator bool()`. Неявное преобразование в |
||
| 864 | целевом типе реализуется при помощи конструктора с единственным аргументом |
||
| 865 | исходного типа. |
||
| 866 | |||
| 867 | Ключевое слово `explicit` может быть применено к конструктору или оператору |
||
| 868 | приведения типов, чтобы гарантировать, что неявное преобразование типов не |
||
| 869 | будет работать. Это применимо не только к неявному преобразованию, но и к |
||
| 870 | спискам инициализации. Например: |
||
| 871 | |||
| 872 | ```c++ |
||
| 873 | class Foo { |
||
| 874 | explicit Foo(int x, double y); |
||
| 875 | ... |
||
| 876 | }; |
||
| 877 | |||
| 878 | void Func(Foo f); |
||
| 879 | |||
| 880 | Func({42, 3.14}); // Ошибка |
||
| 881 | ``` |
||
| 882 | |||
| 883 | Данный код не является примером неявного преобразования типов, но компилятор |
||
| 884 | считает это таковым при использовании ключевого слова `explicit`. |
||
| 885 | |||
| 886 | * Неявное преобразование может сделать тип удобнее или более выразительным |
||
| 887 | путем избавления от необходимости явно указывать целевой тип, когда это |
||
| 888 | очевидно. |
||
| 889 | * Неявное преобразование может быть более простой альтернативой перегрузке |
||
| 890 | функций, например, если функция принимает параметр типа `string_view`, она |
||
| 891 | может принимать параметры типов `std::string` и `const char*`. |
||
| 892 | * Списки инициализации - это компактный и выразительный способ инициализации |
||
| 893 | объектов. |
||
| 894 | |||
| 895 | --- |
||
| 896 | |||
| 897 | * Использование списков инициализации могут скрыть ошибки несоответствия типов. |
||
| 898 | * Неявное преобразование может сделать код более сложным для чтения, особенно |
||
| 899 | когда также присутствуют и виртуальные функции. |
||
| 900 | * Конструкторы с одним аргументом могут быть случайно использованы для |
||
| 901 | приведения типов, даже если это не предполагалось. |
||
| 902 | * Не всегда очевидно, к какому типу данных должно выполняться приведение, |
||
| 903 | особенно в том случае, если несколько типов обеспечивают неявное преобразование. |
||
| 904 | * Списки инициализации могут испытывать те же проблемы, особенно в случае, если |
||
| 905 | в списке один единственный элемент. |
||
| 906 | |||
| 907 | _Вывод:_ |
||
| 908 | Операторы преобразования типов и конструкторы с одним элементом должны быть |
||
| 909 | объявлены как `explicit` в определении классов. Конструкторы копирования и |
||
| 910 | перемещения не должны быть `explicit`, поскольку они не осуществляют приведение |
||
| 911 | типов. |
||
| 912 | |||
| 913 | Неявное преобразование типов может иногда быть необходимы для типов, которые |
||
| 914 | должны быть взаимозаменяемыми. Подобные вопросы должны быть согласованы с |
||
| 915 | начальством. |
||
| 916 | |||
| 917 | Конструкторы с несколькими параметрами могут не использовать `explicit`. |
||
| 918 | Конструкторы, которые используют один параметр `std::initializer_list` |
||
| 919 | не должны использовать `explicit` для поддержки инициализации присваиванием |
||
| 920 | `MyType m = {1, 2};`. |
||
| 921 | |||
| 922 | ### Копируемые и перемещаемые типы данных |
||
| 923 | |||
| 924 | В публичном API класса должно быть явно указано, является ли класс копируемым, |
||
| 925 | перемещаемым, или ни тем, ни другим. Поддержка копирования или перемещения |
||
| 926 | должна осуществляться, если она имеет смысл для этого типа данных. |
||
| 927 | |||
| 928 | *Перемещаемый (movable)* тип может быть инициализирован временным объектом или |
||
| 929 | ему может быть присвоен временный объект. |
||
| 930 | |||
| 931 | *Копируемый (copyable)* тип может быть инициализирован другим объектом такого же |
||
| 932 | типа или присвоен ему с оговоркой, что исходный объект не должен изменяться. Тип |
||
| 933 | `std::unique_ptr`, например, является перемещаемым, но не копируемым. Типы |
||
| 934 | данных `int` или `std::string` - это примеры типов данных, которые являются |
||
| 935 | перемещаемыми и копируемыми. |
||
| 936 | |||
| 937 | Для пользовательских типов данных поведение при копировании определяется в |
||
| 938 | *конструкторе копирования (copy constructor)* и в |
||
| 939 | *операторе присваивания с копированием (copy-assignment operator)*. |
||
| 940 | Поведение при перемещении определяется в |
||
| 941 | *конструкторе перемещения (move constructor)* и в |
||
| 942 | *операторе присваивания с перемещением (move-assignment operator)*, если они |
||
| 943 | присутствуют, либо поведением при копировании в противном случае. |
||
| 944 | |||
| 945 | Конструкторы копирования/перемещения могут быть вызваны компилятором неявно. |
||
| 946 | Например, в случае передачи в функцию по значению. |
||
| 947 | |||
| 948 | Объекты копируемых и перемещаемых типов могут быть переданы и возвращены по |
||
| 949 | значению, что делает API проще, безопаснее и универсальнее. В отличие от |
||
| 950 | передачи объектов через указатель или ссылку, нет риска запутаться с временем |
||
| 951 | жизни, владением, изменчивостью и подобными рисками. Также это предотвращает |
||
| 952 | нелокальное взаимодействие между клиентом и реализацией API, что существенно |
||
| 953 | упрощает понимание, поддержку кода, а также его оптимизацию компилятором. Такие |
||
| 954 | объекты можно использовать с любым API, для которого необходима передача по |
||
| 955 | значению. Например, контейнеры. |
||
| 956 | |||
| 957 | Конструкторы копирования/перемещения и операторы присваивания проще в |
||
| 958 | определении, чем специальные функции, по типу `Clone()`, `CopyFrom()`, `Swap()`, |
||
| 959 | потому что они могут быть сгенерированы компилятором, в том числе, неявно. Они |
||
| 960 | более эффективны, поскольку не требуют выделения памяти на куче, дополнительных |
||
| 961 | присвоений и хорошо оптимизируются. |
||
| 962 | |||
| 963 | Операции перемещения позволяют неявно и эффективно управлять передачей ресурсов |
||
| 964 | через rvalue-объекты. Это позволяет делать код проще. |
||
| 965 | |||
| 966 | Некоторым типам не требуется быть копируемыми, а предоставление операций |
||
| 967 | копирования для таких типов может быть запутанным, нелогичным или неверно |
||
| 968 | понятым. Синглтоны, объекты, привязанные к специфической области видимости |
||
| 969 | (например, `Cleanup`) или сильно связанные с уникальными данными, например |
||
| 970 | (`Mutex`), не могут быть копируемыми по своему смыслу. Операции копирования для |
||
| 971 | базовых классов, имеющих наследников, может перевести к *object slicing*'у - |
||
| 972 | когда свойства из базового класса копируются, а свойства из производного - нет. |
||
| 973 | |||
| 974 | Конструкторы копирования вызываются неявно, поэтому их выполнение легко упустить |
||
| 975 | из виду. Это может привести, например, к снижению производительности из-за |
||
| 976 | лишнего копирования данных. |
||
| 977 | |||
| 978 | Интерфейс каждого класса должен явно определять, какие операции копирования или |
||
| 979 | перемещения класс поддерживает. Это достигается путем явного объявления или |
||
| 980 | удаления (с помощью ключевого слова `delete`) соответствующих операторов в |
||
| 981 | секции `public`. |
||
| 982 | |||
| 983 | В частности, копируемые классы должны явно объявлять операции копирования, а |
||
| 984 | перемещаемые - операции перемещения. В некопируемыех и неперемещаемых классах |
||
| 985 | соответствующие операторы должны быть явным образом удалены. Копируемый класс |
||
| 986 | может также объявить операторы перемещения для поддержки более эффективного |
||
| 987 | способа передачи данных. При объявлении оператора присваивания копирования или |
||
| 988 | перемещения должен быть реализован соответствующий конструктор. |
||
| 989 | |||
| 990 | ```c++ |
||
| 991 | class Copyable { |
||
| 992 | public: |
||
| 993 | Copyable(const Copyable& other) = default; |
||
| 994 | Copyable& operator=(const Copyable& other) = default; |
||
| 995 | |||
| 996 | // Неявное определение операций перемещения будет запрещено |
||
| 997 | // (т.к. объявлено копирование). |
||
| 998 | // Можно определить соответствующие операции явно. |
||
| 999 | }; |
||
| 1000 | |||
| 1001 | class MoveOnly { |
||
| 1002 | public: |
||
| 1003 | MoveOnly(MoveOnly&& other); |
||
| 1004 | MoveOnly& operator=(MoveOnly&& other); |
||
| 1005 | |||
| 1006 | // Неявно определённые операции копирования удаляются. |
||
| 1007 | // Но (если хотите) можно это записать |
||
| 1008 | MoveOnly(const MoveOnly&) = delete; |
||
| 1009 | MoveOnly& operator=(const MoveOnly&) = delete; |
||
| 1010 | }; |
||
| 1011 | |||
| 1012 | class NotCopyableOrMovable { |
||
| 1013 | public: |
||
| 1014 | // Такое объявление запрещает и копирование и перемещение |
||
| 1015 | NotCopyableOrMovable(const NotCopyableOrMovable&) = delete; |
||
| 1016 | NotCopyableOrMovable& operator=(const NotCopyableOrMovable&) = delete; |
||
| 1017 | |||
| 1018 | // Хотя операции перемещения запрещены (неявно), можно записать это явно: |
||
| 1019 | NotCopyableOrMovable(NotCopyableOrMovable&&) = delete; |
||
| 1020 | NotCopyableOrMovable& operator=(NotCopyableOrMovable&&) = delete; |
||
| 1021 | }; |
||
| 1022 | ``` |
||
| 1023 | |||
| 1024 | Описываемые объявления или удаления функций можно опустить в очевидных случаях: |
||
| 1025 | |||
| 1026 | * Если класс не содержит секции `private` например, структура или |
||
| 1027 | класс-интерфейс, то копируемость и перемещаемость определяется копируемостью и |
||
| 1028 | перемещаемостью всех открытых членов. |
||
| 1029 | * Если базовый класс явно некопируемый и неперемещаемый, наследные классы будут |
||
| 1030 | такими же. Однако, если базовый класс не объявляет это операции, то этого будет |
||
| 1031 | недостаточно для прояснения свойств наследуемых классов. |
||
| 1032 | * _На заметку:_ Если конструктор копирования или оператор присваивания |
||
| 1033 | объявлен/удалён, то нужно и явно объявить/удалить оператор копирования, |
||
| 1034 | поскольку его статус неочевиден. Аналогично и для операций перемещения. |
||
| 1035 | |||
| 1036 | Тип не должен быть копируемым/перемещаемым, если смысл данных операций |
||
| 1037 | неочевиден пользователю, или если его реализация влечет к большим затратам. |
||
| 1038 | Операции перемещения для копируемых типов нужны только для оптимизации |
||
| 1039 | производительности и являются потенциальным источником ошибок. Определяйте их |
||
| 1040 | только в том случае, если их использование значительно более эффективно, чем |
||
| 1041 | использование операций копирования. Желательно, чтобы для копирования и |
||
| 1042 | перемещения использовались функции по-умолчанию. Если они используются - |
||
| 1043 | обязательно проверьте, что они работают корректно. |
||
| 1044 | |||
| 1045 | Из-за риска слайсинга стоит избегать открытых операторов копирования и |
||
| 1046 | перемещения для классов, которые планируется использовать в качестве базовых, и |
||
| 1047 | не наследовать классы от классов с оператором присваивания. Старайтесь |
||
| 1048 | наследовать свои классы только от *чистых абстрактных (pure abstract)*. |
||
| 1049 | |||
| 1050 | ### Структуры против классов |
||
| 1051 | |||
| 1052 | Используйте структуры только для пассивных объектов, предназначенных для |
||
| 1053 | хранения данных. Во всех остальных случаях используйте классы. |
||
| 1054 | |||
| 1055 | Ключевые слова `struct` и `class` в языке C++ практически идентичны, но мы |
||
| 1056 | вводим дополнительную семантику для этих ключевых слов. |
||
| 1057 | |||
| 1058 | Структуры должны использоваться для пассивных объектов, предназначенных для |
||
| 1059 | хранения данных. Структуры могут иметь константы. Структуры не должны содержать |
||
| 1060 | инварианты, которые характеризуют отношения между полями, поскольку прямой |
||
| 1061 | доступ к полям может их нарушить. Могут присутствовать конструкторы, |
||
| 1062 | деструкторы и методы-помощники, но они не должны создавать инвариантов. |
||
| 1063 | |||
| 1064 | Если сомневаетесь, используйте `class`. |
||
| 1065 | |||
| 1066 | Для единообразия с STL для `stateless`-типов можно использовать `struct`. |
||
| 1067 | Например, `type_traits` и некоторые функторы. |
||
| 1068 | |||
| 1069 | Не забывайте о том, что у структур и классов разные |
||
| 1070 | [правила об именовании](#Имена-переменных). |
||
| 1071 | |||
| 1072 | ### Структуры против пар и кортежей |
||
| 1073 | |||
| 1074 | Когда элементы в наборе могут иметь содержательные названия, использование |
||
| 1075 | структур является более предпочтительным, чем кортежи или пары. |
||
| 1076 | |||
| 1077 | Хотя использование кортежей и пар позволяет избежать создания пользовательского |
||
| 1078 | типа для хранения набора разнотипных данных, доступ к элементам структуры с |
||
| 1079 | осмысленными названиями лучше *читается*, чем `std::get<X>` или |
||
| 1080 | `.first`, `.second`. Хотя C++14 позволяет обращаться к элементам кортежа по |
||
| 1081 | типу, если он уникален, поля структур намного более информативны. |
||
| 1082 | |||
| 1083 | Пары и кортежи имеет смысл использовать там, где нет больших различий между |
||
| 1084 | полями кортежа или для работ с существующим кодом или API. |
||
| 1085 | |||
| 1086 | ### Наследование |
||
| 1087 | |||
| 1088 | *Композиция* часто предпочтительнее наследования. Когда используете наследование, |
||
| 1089 | делайте его открытым (`public`). |
||
| 1090 | |||
| 1091 | Когда подкласс наследуется от базового класса, он включает все определения всех |
||
| 1092 | данных и операций, определенных в базовом классе. |
||
| 1093 | *Наследованием интерфейса (Interface Inheritance)* будем называть наследование |
||
| 1094 | от *чистого абстрактного базового класса (pure abstract base class)* (без |
||
| 1095 | определенных функций и состояния). Любое другое наследование будем называть |
||
| 1096 | *Наследованием реализации (implementation inheritance)*. |
||
| 1097 | |||
| 1098 | *Наследование реализации* уменьшает размер кода путем повторного использования |
||
| 1099 | кода базового класса путем специализации существующего типа. Поскольку |
||
| 1100 | наследование выполняется в процессе компиляции, программист и компилятор могут |
||
| 1101 | понять операции и обнаружить ошибки. *Наследование интерфейса* может быть |
||
| 1102 | использовано глобально для того, чтобы программно гарантировать, что класс |
||
| 1103 | экспортирует определенный API. Опять же, в данном случае компилятор может в |
||
| 1104 | данном случае обнаружить ошибки в том случае, если класс не определяет нужный |
||
| 1105 | метод API. |
||
| 1106 | |||
| 1107 | Код, использующий *наследование реализации* становится менее понятным, |
||
| 1108 | поскольку реализация методов размазывается между базовым и дочерними классами. |
||
| 1109 | Дочерний класс не может переопределить невиртуальные функции. |
||
| 1110 | |||
| 1111 | Множественное наследование особенно проблематично, поскольку часто влечет за |
||
| 1112 | собой большие накладные расходы по производительности и риски ромбовидного |
||
| 1113 | наследования (*diamond inheritance*), которое может повлечь за собой баги, |
||
| 1114 | неопределенность и путаницу. |
||
| 1115 | |||
| 1116 | Любое наследование должно быть открытым (*public*). Если у вас возникает |
||
| 1117 | потребность в закрытом наследовании, то необходимо создать закрытый член класса |
||
| 1118 | который вы хотите сделать базовым. Можно использовать ключевое слово `final` на |
||
| 1119 | классах, наследование от которых должно быть запрещено. |
||
| 1120 | |||
| 1121 | Не злоупотребляйте *наследованием реализации*. Композиция часто является более |
||
| 1122 | предпочтительной. Используйте семантику *является*. Класс `Bar` стоит |
||
| 1123 | наследовать от `Foo`, если можно дать утвердительный ответ на вопрос `Bar` |
||
| 1124 | является `Foo`? |
||
| 1125 | |||
| 1126 | Ограничьте использование защищенных (`protected`) функций, которые должны быть |
||
| 1127 | доступны подклассам. Помните, [данные должны быть закрытыми](#Контроль-доступа) |
||
| 1128 | (`private`). |
||
| 1129 | |||
| 1130 | Множественное наследование разрешено, но множественное *наследование реализации* |
||
| 1131 | не рекомендуется. |
||
| 1132 | |||
| 1133 | ### Перегрузка операторов |
||
| 1134 | |||
| 1135 | Перегружайте операторы в рамках разумного. Не используйте пользовательские |
||
| 1136 | литералы. |
||
| 1137 | |||
| 1138 | С++ позволяет программисту определять своим версии встроенных операторов, |
||
| 1139 | используя для этого ключевое слово `operator` и пользовательский тип, |
||
| 1140 | как один из параметров. Также ключевое слово `operator` позволяет создавать |
||
| 1141 | пользовательские литералы, используя `operator""` и операторы приведения типов, |
||
| 1142 | например `operator bool()`. Это называется *перегрузкой операторов*. |
||
| 1143 | |||
| 1144 | Использование перегрузки операторов может сделать код более выразительным и |
||
| 1145 | интуитивным, когда пользовательские типы ведут себя как встроенные. |
||
| 1146 | Перегружаемые операторы соответствуют определенным операциям и следование логике |
||
| 1147 | применения этой операции может сделать работу с пользовательскими типами более |
||
| 1148 | читаемой, а также использовать их при работе с библиотеками, которые используют |
||
| 1149 | данные операторы. |
||
| 1150 | |||
| 1151 | Пользовательские литералы - это выразительный способ создания объектов |
||
| 1152 | пользовательских типов. |
||
| 1153 | |||
| 1154 | * Предоставление корректного, согласованного и логичного набора операторов для |
||
| 1155 | пользовательского типа требует усилий и может обернуться запутыванием и багами. |
||
| 1156 | * Злоупотребление перегрузкой операторов может привести к непонятному коду, |
||
| 1157 | особенно, если эти семантика данных операторов не соответствует общепринятой. |
||
| 1158 | * Все проблемы, связанные с перегрузкой функций распространяются и на |
||
| 1159 | перегрузку операторов. |
||
| 1160 | * Перегрузка операторов может быть не интуитивной в плане производительности, |
||
| 1161 | поскольку встроенные операции очень производительны. |
||
| 1162 | * Поиск в коде вызовов перегруженных операций требует особых утилит поиска, |
||
| 1163 | понимающих синтаксис C++. |
||
| 1164 | * Если запутаться с типами операндов перегружаемых операций, можно получить |
||
| 1165 | другой перегруженный оператор, а не ошибку компиляции, например `foo < bar` и |
||
| 1166 | `&foo < &bar`. |
||
| 1167 | * Перегрузка некоторых операторов является раскованной сама по себе. Например, |
||
| 1168 | перегрузка унарного `&`. Перегрузка операторов `&&`, `||`, `,` не порядку |
||
| 1169 | исполнения встроенных операций. |
||
| 1170 | * Операторы могут определяться вне класса, поэтому есть риск того, что разные |
||
| 1171 | файлы могут содержать разное определение одного и того же оператора. Это может |
||
| 1172 | привести к трудноуловимым ошибкам времени исполнения. |
||
| 1173 | * Пользовательские литералы создают новые синтаксические формы, которые будут |
||
| 1174 | незнакомы даже продвинутым программистам. Например: `"Hello World"sv` как |
||
| 1175 | сокращение для `std::string_view(«Hello World»)`. |
||
| 1176 | * Т. к. для пользовательских литералов не указывается пространство имен, |
||
| 1177 | придется использовать либо using-директиву, |
||
| 1178 | [которая запрещена](#Пространства-Имен), либо using-объявление, запрещенное в |
||
| 1179 | заголовочных файлах. |
||
| 1180 | |||
| 1181 | _Выводы:_ |
||
| 1182 | |||
| 1183 | Используйте перегрузку операторов только тогда, когда их значение очевидно, |
||
| 1184 | предсказуемо и согласовано со встроенными операциями. Например, переопределяйте |
||
| 1185 | оператор `|` как логическое `ИЛИ`, а не как перенаправление потока. |
||
| 1186 | |||
| 1187 | Перегружайте операторы только для своих типов данных. Определяйте их в той же |
||
| 1188 | паре `.hpp`/`.cpp` - файлов, что и тип данных, к которому они относятся. Таким |
||
| 1189 | образом операции будут доступны тогда же, когда и исходный тип данных, а риск |
||
| 1190 | нескольких определений операторов будет минимизирован. По возможности избегайте |
||
| 1191 | определения шаблонных (`template`) операторов, поскольку они должны |
||
| 1192 | соответствовать определенным правилам для всех аргументов. При перегрузке |
||
| 1193 | операторов, перегружайте все связанные операторы, например, при перегрузке |
||
| 1194 | оператора `<`, необходимо перегрузить все операторы сравнения. Убедитесь, что |
||
| 1195 | они работают согласовано. Например, что операторы `<` и `>` |
||
| 1196 | никогда не возвращают `true` одновременно. |
||
| 1197 | |||
| 1198 | По возможности, не делайте перегруженные операторы функциями-членами классов. |
||
| 1199 | Если бинарный операнд определен как член класса, риски неявного преобразования |
||
| 1200 | существуют только для правого оператора, а если как глобальная функция - для |
||
| 1201 | обоих. Будет странно, если `a < b` компилируется, а `b > a` - нет. |
||
| 1202 | |||
| 1203 | Не заходите слишком далеко, избегая перегрузки операторов. Перегрузка |
||
| 1204 | операторов предпочтительнее, чем функции, по типу |
||
| 1205 | `equals(), copyFrom(), printTo()`. И наоборот, не перегружайте оператор только |
||
| 1206 | из-за того, что он нужен какой-то библиотеке. Например, если вы хотите |
||
| 1207 | использовать `std::set` для типа, для которого не предусмотрено логикой |
||
| 1208 | операций больше или меньше, используйте свою функцию-компаратор вместо |
||
| 1209 | перегрузки оператора `<`. |
||
| 1210 | |||
| 1211 | Не перегружайте операторы `&&, ||, ,(запятая)` и унарный `&`. |
||
| 1212 | Не используйте пользовательские литералы. |
||
| 1213 | |||
| 1214 | Операторы преобразования типов обсуждаются и в секции про |
||
| 1215 | [неявное преобразование типов](#неявное-преобразование-типов). |
||
| 1216 | Оператор `=` затрагивается в секции про |
||
| 1217 | [конструкторы копирования](#копируемые-и-перемещаемые-типы-данных). |
||
| 1218 | Перегрузка оператора `<<` затрагивается в секции про |
||
| 1219 | [потоки](#потоки-ввода-вывода). Также обратите внимание не секцию про |
||
| 1220 | [перегрузку функций](#перегрузка-функций), которая затрагивает перегрузку в |
||
| 1221 | целом. |
||
| 1222 | |||
| 1223 | ### Контроль доступа |
||
| 1224 | |||
| 1225 | Члены-данные класса должны быть закрытыми (`private`), если это не константы |
||
| 1226 | (`const`). Это упрощает работу с инвариантами ценой создания простых |
||
| 1227 | клишированных функций (get/set-ассессоров), если не обходимо. |
||
| 1228 | |||
| 1229 | По техническим причинам члены данных фикстур для Google Test могут быть |
||
| 1230 | `protected`. |
||
| 1231 | |||
| 1232 | ### Порядок объявления |
||
| 1233 | |||
| 1234 | Группируйте похожие объявления. |
||
| 1235 | |||
| 1236 | Определение класса обычно начинается с секции `public:`, за которой следует |
||
| 1237 | `protected:`, а в конце - `private:`. Избегайте пустых секций. |
||
| 1238 | |||
| 1239 | Внутри каждой секции группируйте похожие виды объявлений вместе. |
||
| 1240 | Предпочтительным является такой порядок: |
||
| 1241 | |||
| 1242 | 1. Типы данных и псевдонимы (`typedef`, `using`, `enum`, вложенные |
||
| 1243 | структуры и классы, `friend`-типы данных). |
||
| 1244 | 2. Статические константны. |
||
| 1245 | 3. Фабричные методы |
||
| 1246 | 4. Конструкторы и операторы присваивания |
||
| 1247 | 5. Деструктор |
||
| 1248 | 6. Все остальные функции (статические и нестатические функции-члены, |
||
| 1249 | дружественные функции). |
||
| 1250 | 7. Свойства класса (Члены-данные) (статические и нет). |
||
| 1251 | |||
| 1252 | Не размещайте определения больших методов внутри классов. Обычно только |
||
| 1253 | тривиальные или критичные к производительности функции могут быть определены |
||
| 1254 | как `inline`. См. [здесь](#inline-функции). |
||
| 1255 | |||
| 1256 | ## Функции |
||
| 1257 | |||
| 1258 | ### Входные и выходные данные |
||
| 1259 | |||
| 1260 | Как правило, выходные данные функции предоставляются через возвращаемое |
||
| 1261 | значение, иногда через выходные (или входные-выходные) параметры. |
||
| 1262 | |||
| 1263 | Использование возвращаемых значений предпочтительнее, поскольку они улучшают |
||
| 1264 | читаемость, при этом часто демонстрируя такую же или лучшую производительность. |
||
| 1265 | |||
| 1266 | Возвращение значения предпочтительно, если не получается, используйте |
||
| 1267 | возвращение по ссылке. Избегайте использования возвращения по указателю, |
||
| 1268 | если тот не может быть `null`. |
||
| 1269 | |||
| 1270 | Параметры функций могут быть входными, выходными или и теми, и другими. |
||
| 1271 | Обязательные входные параметры, как правило, передаются по значению или |
||
| 1272 | константной ссылке. Выходные параметры должны передаваться по неконстантной |
||
| 1273 | ссылке, если они не могут быть `null`. В общем случае, используйте |
||
| 1274 | `std::optional` для необязательных входных параметров и константный указатель, |
||
| 1275 | если обязательный параметр был бы ссылкой (поскольку он `nullable`). |
||
| 1276 | Используйте неконстантные указателя для необязательных выходных или |
||
| 1277 | входных-выходных параметров. |
||
| 1278 | |||
| 1279 | Избегайте использований константных ссылок **только** для того, чтобы продлить |
||
| 1280 | время жизни временных объектов (константные ссылки могут расширять время жизни |
||
| 1281 | и могут быть привязаны к временным объектам). Вместо этого постарайтесь найти |
||
| 1282 | способ избавиться от требований к времени жизни путем копирования объекта или |
||
| 1283 | используйте указатель, задокументировав время жизни и требования к тому, чтобы |
||
| 1284 | он не был `null`. |
||
| 1285 | |||
| 1286 | При объявлении параметров функции старайтесь сначала объявлять входные |
||
| 1287 | параметры функций, а потом выходные. Это не жесткое правило. |
||
| 1288 | |||
| 1289 | ### Пишите короткие функции |
||
| 1290 | |||
| 1291 | Пишите короткие и концентрированные функции. |
||
| 1292 | |||
| 1293 | > *Функция должна делать одну вещь и делать ее хорошо!* - (c) Дядя Боб. |
||
| 1294 | |||
| 1295 | Иногда длинная функция бывает полезной, но если размер функции превышает 40 |
||
| 1296 | строк - это повод задуматься над тем, чтобы разбить ее на несколько. |
||
| 1297 | |||
| 1298 | Длинные функции тяжело читаются и сложнее отлаживаются. С длинными функциями |
||
| 1299 | сложнее работать в команде, поскольку будут возникать проблемы при слиянии. |
||
| 1300 | Короткие функции легче тестировать. |
||
| 1301 | |||
| 1302 | При работе со старыми функциями, которые кажутся слишком длинными и запутанными, |
||
| 1303 | не бойтесь ее модифицировать: если работа с такой функцией затруднена или |
||
| 1304 | неудобна, если в ней есть ошибки, которые трудно отлаживать или если кусочки |
||
| 1305 | данной функции можно применить в другом месте, не бойтесь разбивать ее на |
||
| 1306 | меньшие и более управляемые куски. |
||
| 1307 | |||
| 1308 | Как правило, функция должна иметь не более 3х параметров. Особенно, если она |
||
| 1309 | экспортируется из API. Существуют исключения, например, когда функцию совершенно |
||
| 1310 | необходимо сделать `stateless`, но это "дурно пахнет". |
||
| 1311 | |||
| 1312 | ### Перегрузка функций |
||
| 1313 | |||
| 1314 | Используйте перегрузку функций (в т. ч., конструкторов) только если читатель, |
||
| 1315 | смотря на код ее вызова будет хорошо понимать, что происходит без точного |
||
| 1316 | определения, какая именно перегрузка вызывается. |
||
| 1317 | |||
| 1318 | Например, можно написать функцию, которая принимает аргумент |
||
| 1319 | `const std::string&` и перегрузить другой функцией, которая принимает |
||
| 1320 | `const char*`. Так или иначе, в данном случае лучше использовать |
||
| 1321 | `std::string_view`. |
||
| 1322 | |||
| 1323 | ```c++ |
||
| 1324 | class MyClass { |
||
| 1325 | public: |
||
| 1326 | void Analyze(const std::string &text); |
||
| 1327 | void Analyze(const char *text, size_t textlen); |
||
| 1328 | }; |
||
| 1329 | ``` |
||
| 1330 | |||
| 1331 | Перегрузка функций может быть более интуитивной, позволяя иметь функции с одним |
||
| 1332 | названием, применяя разные аргументы. Это может быть необходимо для шаблонного |
||
| 1333 | кода и это удобно для реализации паттерна `Visitor`. |
||
| 1334 | |||
| 1335 | Перегрузка, основанная на использовании квалификаторов `const` или `&` может |
||
| 1336 | сделать кода более удобным и эффективным. |
||
| 1337 | |||
| 1338 | Пользователь должен хорошо знать правила C++ по подстановке типов, если функция |
||
| 1339 | перегружается только по типу аргументов, а не по количеству. Также перегрузка |
||
| 1340 | может быть запутанной, а также при наследовании, если класс-наследник |
||
| 1341 | переопределяет одну виртуальную функцию, но не переопределяет перегруженную. |
||
| 1342 | |||
| 1343 | _Вывод:_ |
||
| 1344 | Перегружайте функцию только тогда, когда нет семантической разницы между |
||
| 1345 | перегруженными вариантами. Перегруженные операторы могут отличаться по типам, |
||
| 1346 | квалификаторам, и количеству аргументов. Пользователь не должен разбираться, |
||
| 1347 | какая конкретно перегрузка была вызвана, только то, что была вызвана какая-то |
||
| 1348 | реализация из множества была вызвана. |
||
| 1349 | |||
| 1350 | > Если вы можете задокументировать все |
||
| 1351 | > перегрузки одним комментарием в заголовочном файле, это хороший знак, |
||
| 1352 | > что данное множество перегруженных функций было спроектировано хорошо. |
||
| 1353 | |||
| 1354 | ### Аргументы по-умолчанию |
||
| 1355 | |||
| 1356 | Аргументы по-умолчанию разрешены в невиртуальных функциях и тогда, когда |
||
| 1357 | аргумент по-умолчанию всегда имеет одно и тоже значение. Следуйте тем же |
||
| 1358 | ограничениям, что и при [перегрузке](#перегрузка-функций). Перегрузка функций |
||
| 1359 | предпочтительнее, чем аргументы по-умолчанию в тех случаях, когда преимущество |
||
| 1360 | по читаемости аргументы по-умолчанию не перевешивает их недостатков. |
||
| 1361 | |||
| 1362 | Часто бывает так, что у вас есть функция, использующая какие-то значения, но |
||
| 1363 | *иногда* для них необходимо переопределить. Используя аргументы по-умолчанию, |
||
| 1364 | это можно сделать без объявления нескольких функций для редких исключений. В |
||
| 1365 | сравнении с перегрузкой функций, синтаксис аргументов по-умолчанию чище, |
||
| 1366 | с меньшим количеством нефункционального кода и явным отделением "обязательных" |
||
| 1367 | аргументов от "необязательных". |
||
| 1368 | |||
| 1369 | Аргументы по-умолчанию - это еще один способ добиться семантики, похожей на |
||
| 1370 | перегрузку функций. Поэтому причины [не делать функции перегруженными] |
||
| 1371 | (#перегрузка-функций) распространяются и на них. |
||
| 1372 | |||
| 1373 | В случае с виртуальными функциями значение аргумента по-умолчанию определяется |
||
| 1374 | статическим типом целевого объекта и нет никаких гарантий, что функции, |
||
| 1375 | определенные во всех классах-наследниках будут иметь одно и то же значение |
||
| 1376 | аргумента по-умолчанию. |
||
| 1377 | |||
| 1378 | Значение аргумента по-умолчанию вычисляется каждый раз при вызове функции, что |
||
| 1379 | может увеличить объем генерируемого кода. Читатель может ожидать, что такой |
||
| 1380 | аргумент принимает одно и то же значение, что может сбить с толку. |
||
| 1381 | |||
| 1382 | Указатель на функции с аргументами по-умолчанию, поскольку они должны |
||
| 1383 | присутствовать. Сигнатура функции может отличаться от сигнатуры вызова. |
||
| 1384 | Перегрузка функций позволяет избежать этой проблемы. |
||
| 1385 | |||
| 1386 | Аргументы по-умолчанию запрещены в виртуальных функциях, где они не работают |
||
| 1387 | должным образом, и в случаях, когда значение по-умолчанию может измениться в |
||
| 1388 | зависимости от места, где вызов был сделан. Например, не употребляйте такие |
||
| 1389 | конструкции: `void f(int n = counter++);`. |
||
| 1390 | |||
| 1391 | В ряде случаев аргументы по-умолчанию могут улучшить читаемость объявлений |
||
| 1392 | функции. Если сомневаетесь, используйте перегрузку. |
||
| 1393 | |||
| 1394 | ### Новый синтаксис возвращаемых значений |
||
| 1395 | |||
| 1396 | Используйте указание типа возвращаемого значение в конце функции только если |
||
| 1397 | использование общепринятого синтаксиса непрактично или ухудшает читаемость. |
||
| 1398 | |||
| 1399 | C++ разрешает использовать две формы объявления функций. В общепринятой форме |
||
| 1400 | возвращаемый тип появляется перед названием функции, Например: |
||
| 1401 | |||
| 1402 | ``` c++ |
||
| 1403 | int foo(int x); |
||
| 1404 | ``` |
||
| 1405 | |||
| 1406 | Новая форма использует ключевое слово `auto` перед названием функции, а тип |
||
| 1407 | возвращаемого значения - после списка аргументов. Например: |
||
| 1408 | |||
| 1409 | ``` c++ |
||
| 1410 | auto foo(int x) -> int; |
||
| 1411 | ``` |
||
| 1412 | |||
| 1413 | Тип возвращаемого значения, указанного в конце функции находится уже в области |
||
| 1414 | видимости функции. Для простых типов данных (например, `int`) большой разницы |
||
| 1415 | нет, но она появляется в более сложных случаях, например, для типов из области |
||
| 1416 | видимости класса или типов, записанных через параметры функции. |
||
| 1417 | |||
| 1418 | Тип возвращаемого значения в конце функции - это единственный способ явно |
||
| 1419 | указать возвращаемый тип [лямбда-выражения](#лямбда-выражения). Компилятор не |
||
| 1420 | всегда может вывести возвращаемый тип лямбда-выражения. Даже если компилятор |
||
| 1421 | способен вывести тип возвращаемого значения автоматически, иногда его требуется |
||
| 1422 | указать явно, например, для улучшения читаемости кода. |
||
| 1423 | |||
| 1424 | Иногда указание типа возвращаемой функции более читаемо и удобно, особенно |
||
| 1425 | когда тип возвращаемого значения зависит от параметров шаблонов. Например: |
||
| 1426 | |||
| 1427 | ``` c++ |
||
| 1428 | template <typename T, typename U> |
||
| 1429 | auto add(T t, U u) -> decltype(t + u); |
||
| 1430 | ``` |
||
| 1431 | |||
| 1432 | Понятнее, чем |
||
| 1433 | |||
| 1434 | ```c++ |
||
| 1435 | template <typename T, typename U> |
||
| 1436 | decltype(declval<T&>() + declval<U&>()) add(T t, U u); |
||
| 1437 | ``` |
||
| 1438 | |||
| 1439 | Тип возвращаемого значения в конце функции - сравнительно новая особенность |
||
| 1440 | языка C++, не имеющая аналого в других C-подобных языках, поэтому некоторые |
||
| 1441 | читатели могут быть незнакомы с такой формой записи. |
||
| 1442 | |||
| 1443 | Существующая кодовая база содержит огромное количество старого кода. |
||
| 1444 | Маловероятно, что весь этот код будет переписан под новый синтаксис, так что |
||
| 1445 | реалистично предположить, что что будет использоваться либо старый синтаксис, |
||
| 1446 | либо оба. Единообразие в данном случае предпочтительнее. |
||
| 1447 | |||
| 1448 | В большинстве случаев от нового синтаксиса возвращаемого значения нет особой |
||
| 1449 | пользы, за исключением определения возвращаемого значения лямбда-функций или тех |
||
| 1450 | случаев, когда такое написание поможет более читаемо объявить тип возвращаемого |
||
| 1451 | значения. Последнее зачастую касается сложных шаблонов, использование которых |
||
| 1452 | в большинстве случаев [не приветствуется](#шаблонное-метапрограммирование). |
||
| 1453 | |||
| 1454 | ## Специфическая магия |
||
| 1455 | |||
| 1456 | В данной секции указаны дополнительные рекомендации по повышению надежности |
||
| 1457 | кода на C++. |
||
| 1458 | |||
| 1459 | ### Владение и умные указатели |
||
| 1460 | |||
| 1461 | Предпочтительно, чтобы у динамических объектов был один, конкретный владелец. |
||
| 1462 | Передача объектов через умные указатели предпочтительна. |
||
| 1463 | |||
| 1464 | *Владение (ownership)* - это технология для управления выделенной памятью и |
||
| 1465 | другими ресурсами. Владелец динамического объекта - это объект или функция, |
||
| 1466 | которая отвечает за гарантию освобождения объекта, когда в нем больше нет |
||
| 1467 | необходимости. Владение может быть *совместным (shared ownership)*, в таком |
||
| 1468 | случае за его освобождение отвечает последний использующий объект. Даже в том |
||
| 1469 | случае, если владение не совместное, оно может быть передано от одной сущности к |
||
| 1470 | другой. |
||
| 1471 | |||
| 1472 | *Умные указатели (smart pointers)* - это классы, которые ведут себя как |
||
| 1473 | указатели, то есть, перегружают операторы `*` и `->`. Некоторые умные указатели |
||
| 1474 | могут использоваться для автоматизации управления владением. Шаблон класса |
||
| 1475 | `std::unique_ptr` - это умный указатель, который отвечает за единоличное |
||
| 1476 | владение: Динамический объект будет уничтожен, когда `std::unique_ptr` выйдет за |
||
| 1477 | пределы области видимости. Данный тип не копируемый, но переносимый для |
||
| 1478 | осуществления механизма передачи владения от одного объекта к другому. Тип |
||
| 1479 | `std::shared_ptr`- это умный указатель, который отвечает за совместное владение |
||
| 1480 | динамическим объектом. Объекты этого типа подлежат копированию, а выделенная |
||
| 1481 | память освободится с уничтожением последнего объекта `std::shared_ptr`. |
||
| 1482 | * Управлять динамически выделяемыми ресурсами физически невозможно без |
||
| 1483 | какой-либо логики владения. |
||
| 1484 | * Передача владения может быть дешевле, чем копирование объекта (в тех случаях, |
||
| 1485 | если оно возможно). |
||
| 1486 | * Передача владения может быть проще, чем передача указателя или ссылки, |
||
| 1487 | поскольку это уменьшает необходимость в координации времени жизни объектов. |
||
| 1488 | * Умные указатели делают управление владения явным и самодокументированным, что |
||
| 1489 | упрощает читаемость кода. |
||
| 1490 | * Умные указатели могут целиком взять на себя управления владением, |
||
| 1491 | упрощая код, и избавляя от необходимости управления большим количеством ошибок. |
||
| 1492 | * Для константных объектов, совместное владением может быть простой и эффективной заменой глубокого копирования. |
||
| 1493 | |||
| 1494 | --- |
||
| 1495 | |||
| 1496 | * Владение представляется через указатели, неважно, умные или нет. |
||
| 1497 | Семантика указателей сложнее семантики значений, особенно в API: нужно |
||
| 1498 | заботиться не только о владении, но и об именовании, времени жизни, |
||
| 1499 | изменчивости (*mutability*), помимо всего прочего. |
||
| 1500 | * Затраты производительности на работу со значениями часто переоценивается, а |
||
| 1501 | выигрыш в производительности от передачи владения может не оправдать усложнения |
||
| 1502 | читаемости и увеличения сложности. |
||
| 1503 | * API, которое передает владение, по сути, решает за клиентов, какую модель |
||
| 1504 | управления памятью использовать. |
||
| 1505 | * Умные указатели менее явно выражают, где и когда именно будут освобождены |
||
| 1506 | ресурсы. |
||
| 1507 | * Передача управления через механизмы перемещения, которые использует |
||
| 1508 | `std::unique_ptr` являются сравнительно новыми и могут запутать некоторых |
||
| 1509 | программистов. |
||
| 1510 | * Совместное владение - это соблазнительная альтернатива тщательному |
||
| 1511 | проектированию владения объектов, которая скрывает дизайн системы. |
||
| 1512 | * Совместное владение осуществляет управление владения во время исполнения, |
||
| 1513 | что довольно затратно по ресурсам. |
||
| 1514 | * В некоторых случаях (например, при циклических ссылках), объекты с |
||
| 1515 | совместным владением могут никогда не быть уничтожены. |
||
| 1516 | * Умные указатели - не идеально полноценная замена для обычных. |
||
| 1517 | |||
| 1518 | Если динамическое выделение памяти необходимо, предпочитайте держать владение в |
||
| 1519 | той сущности, которая ее выделила. Если другой сущности необходимо иметь |
||
| 1520 | доступ к объекту, рассмотрите возможность передачи копии или передачи ссылки |
||
| 1521 | или указателя без передачи владения. Для того, чтобы сделать передачу владения |
||
| 1522 | более явной, предпочтительнее использовать `std::unique_ptr`. Например: |
||
| 1523 | |||
| 1524 | ``` c++ |
||
| 1525 | std::unique_ptr<Foo> FooFactory(); |
||
| 1526 | void FooConsumer(std::unique_ptr<Foo> ptr); |
||
| 1527 | ``` |
||
| 1528 | |||
| 1529 | Не используйте совместное владение без очень хорошей причины. |
||
| 1530 | Например, избежать дорогостоящих операций копирования, но стоит убедиться, что |
||
| 1531 | прирост производительности стоит того, а объект *неизменяемый (immutable)*, |
||
| 1532 | (например, `std::shared_ptr<const Foo>`). Если нужно *именно* разделяемое |
||
| 1533 | владение, используйте `std::shared_ptr`. |
||
| 1534 | |||
| 1535 | Никогда не используйте `std::auto_ptr`. |
||
| 1536 | |||
| 1537 | > _Внимание:_ Qt использует свои умные указатели, например, `QSharedPtr`, |
||
| 1538 | > которые удобно использовать с данной библиотекой. Однако, конвертация из |
||
| 1539 | > одного умного указателя в другой невозможна. Старайтесь использовать |
||
| 1540 | > `std::shared_ptr`, где это возможно, для обеспечения единообразия. Если же |
||
| 1541 | > проект тесно связан с Qt, можно использовать классы из Qt. |
||
| 1542 | > Единообразие здесь - самое важное. |
||
| 1543 | |||
| 1544 | ### Физический уровень |
||
| 1545 | |||
| 1546 | ### Утилиты автоматизации |
||
| 1547 | |||
| 1548 | ### Дополнительные стандарты |
||
| 1549 | |||
| 1550 | ## Другие особенности С++ |
||
| 1551 | |||
| 1552 | ### Ссылки на rvalue |
||
| 1553 | |||
| 1554 | Используйте *rvalue-ссылки (rvalue references)* только в некоторых особых |
||
| 1555 | случаях, описанных ниже. |
||
| 1556 | |||
| 1557 | Rvalue-ссылки - это ссылки, которые могут привязываться только ко временным |
||
| 1558 | объектам. Их синтаксис похож на синтаксис обычных ссылок. Например, |
||
| 1559 | `void f(std::string&& s);` - это объявление функции, аргумент которой является |
||
| 1560 | rvalue-ссылкой на `std::string`. |
||
| 1561 | |||
| 1562 | Когда суффикс `&&` без дополнительных квалификаторов используется с шаблонным |
||
| 1563 | аргументом функции, то применяются специальные правила вывода типа аргумента. |
||
| 1564 | Такая ссылка имеет название *forwarding reference*[^1]. |
||
| 1565 | |||
| 1566 | * Определение конструктора копирования делает возможность перемещать значение |
||
| 1567 | вместо копирования. Например, если `v1` имеет тип `std::vector<std::string>`, |
||
| 1568 | например, конструкция `auto v2(std::move(v1))`, вероятно приведет к простой |
||
| 1569 | манипуляции с памятью вместо копирования большого количества данных. |
||
| 1570 | * Ссылки на rvalue позволяют определять перемещаемые, но не копируемые типы |
||
| 1571 | данных. |
||
| 1572 | * `std::move` необходим для эффективного использования некоторых библиотечных |
||
| 1573 | типов, например, `std::unique_ptr`. |
||
| 1574 | * Механизм *forwarding reference*, который использует ссылку rvalue-ссылку, |
||
| 1575 | делает возможным разработку шаблонной функции, которая перенаправляет аргументы |
||
| 1576 | другой функции и работает с ними, независимо от того, какая именно ссылка |
||
| 1577 | пришла в качестве аргумента. Это называется *perfect forwarding*. |
||
| 1578 | |||
| 1579 | --- |
||
| 1580 | |||
| 1581 | * Ссылки на rvalue все еще не до конца понятны большинству программистов. Такие |
||
| 1582 | правила, как *сжатие ссылок (reference collapsing)* и специальные правила |
||
| 1583 | вывода типов данных для *forwarding reference* остаются неясными. |
||
| 1584 | * Ссылки на rvalue часто используются неправильно. Использование rvalue-ссылок |
||
| 1585 | в сигнатурах, когда аргумент должен оставаться валидным или когда не было |
||
| 1586 | выполнения операции `std::move`, является неинтуитивным. |
||
| 1587 | |||
| 1588 | Не используйте rvalue-ссылки и не используйте квалификатор `&&` в методах, |
||
| 1589 | за исключением следующих случаях: |
||
| 1590 | * Определение конструкторов перемещения |
||
| 1591 | (см. [соответствующую секцию](#копируемые-и-перемещаемые-типы-данных)). |
||
| 1592 | * Используйте `&&`-методы, которые логически "поглощают" `*this` и оставляют |
||
| 1593 | его в неиспользуемом или пустом состоянии. Обратите внимание, что это относится |
||
| 1594 | только к квалификаторам методов (после закрывающей скобки в сигнатуре функций). |
||
| 1595 | Если необходимо поглотить параметр функции, попробуйте передавать его по |
||
| 1596 | значению. |
||
| 1597 | * Для обеспечения *perfect forwarding* совместно с `std::forward`. |
||
| 1598 | * Для определения пар перегрузок, например `foo(const Bar&)` и `foo(Bar&&)`. |
||
| 1599 | Чаще всего, проще передать параметр по значению, но перегруженная пара функций |
||
| 1600 | часто является более производительной или необходима ,когда код должен |
||
| 1601 | работать с большим количеством типов данных. Как всегда: если для оптимизации |
||
| 1602 | нужно написать более сложный код, то нужно убедиться, что это |
||
| 1603 | действительно помогает. |
||
| 1604 | |||
| 1605 | [^1]: Не нашел нормального перевода |
||
| 1606 | |||
| 1607 | ### Дружественные функции и классы |
||
| 1608 | |||
| 1609 | Дружественные (`friend`) функции и классы разрешены, если на это есть веская |
||
| 1610 | причина. |
||
| 1611 | |||
| 1612 | Друзья должны быть объявлены в том же файле, чтобы для читателя не было |
||
| 1613 | необходимости читать другой файл, чтобы найти, как именно используются закрытые |
||
| 1614 | методы класса. Обычное использование дружественных классов - это, например, в |
||
| 1615 | классе `FooBuilder`, который будет другом `Foo` и задача которого корректно |
||
| 1616 | сконструировать внутреннее состояние класса `Foo`. Также это может быть полезно |
||
| 1617 | при модульном тестировании. |
||
| 1618 | |||
| 1619 | Друзья классов расширяют, но не нарушают границы инкапсуляции класса. В |
||
| 1620 | некоторых случаях это лучше, чем делать член публичным только если одна функция |
||
| 1621 | или класс должны иметь к нему доступ. Как бы то ни было, большинство классов |
||
| 1622 | должны взаимодействовать друг с другом через публичных членов. |
||
| 1623 | |||
| 1624 | ### Исключения |
||
| 1625 | |||
| 1626 | *Несмотря на то, что этот стиль кодирования основан на стиле кодирование* |
||
| 1627 | *Google, мы используем исключения.* |
||
| 1628 | |||
| 1629 | * Исключения позволяют обрабатывать ситуации типа «это невозможно» в функциях с |
||
| 1630 | очень большим уровнем вложенности без неясной и подверженной ошибкам работы по |
||
| 1631 | распределению кодов ошибок. |
||
| 1632 | * Исключения используются в большинстве современных языков программирования и |
||
| 1633 | их использование в C++ позволяет писать код, концептуально схожий с Python, |
||
| 1634 | Java и др. |
||
| 1635 | * Некоторые библиотеки C++ используют исключения в своей работе и отказ от них |
||
| 1636 | может существенно усложнить интеграцию с ними. |
||
| 1637 | * Исключения являются единственным способом сообщения об ошибках в |
||
| 1638 | конструкторе. Можно обойти это используй фабричные методы, метод `Init()` или |
||
| 1639 | специальное, невалидное состояние, но это потребует либо выделения памяти, либо |
||
| 1640 | особого состояния. |
||
| 1641 | * Исключения полезны в каркасах тестирования. |
||
| 1642 | * Исключения позволяют сепарировать код, который генерирует ошибку от кода, |
||
| 1643 | который их обрабатывает. |
||
| 1644 | * Механизм исключений хорошо ложится на формализацию требований к программе в |
||
| 1645 | виде *вариантов использования (use cases)*. |
||
| 1646 | |||
| 1647 | --- |
||
| 1648 | |||
| 1649 | * Когда вы добавляете выражение `throw` к существующей функции, нужно проверить |
||
| 1650 | всех, кто эту функцию вызывает. Необходимо либо обеспечить базовый уровень |
||
| 1651 | безопасных исключений (*exception safety levels*), либо нормально относиться к |
||
| 1652 | аварийному завершению работы программы. Если `f()` вызывает `g()`, который |
||
| 1653 | вызывает `h()`, а в `h` выбрасывается исключение, которое ловится в `f`, то `g` |
||
| 1654 | может не очищаться так, как нужно. |
||
| 1655 | * Исключения делают сложным понимание потока управления, поскольку у функции |
||
| 1656 | есть, по-сути, несколько мест завершения. |
||
| 1657 | * Безопасность исключений требует применения дополнительных практик, например, |
||
| 1658 | RAII. |
||
| 1659 | * Использование исключений ведёт к распуханию бинарного файла программы, |
||
| 1660 | увеличивает время компиляции, и вообще может привести к проблемам с адресным |
||
| 1661 | пространством. |
||
| 1662 | * Доступность исключений может провоцировать разработчиков выбрасывать их по |
||
| 1663 | поводу и без. Например, ввод неверного текста, очевидно, не должен приводить к |
||
| 1664 | возникновению исключения и т.п. |
||
| 1665 | |||
| 1666 | Для того, чтобы обработка исключений была максимально безболезненной, |
||
| 1667 | используйте парадигму RAII и новые механизмы исключений, такие как |
||
| 1668 | `std::exception_ptr`, `std::nested_exception`, `std::current_exception`, |
||
| 1669 | `std::rethrow_exception`, `std::nested_exception` и т. д. |
||
| 1670 | |||
| 1671 | Наследуйте классы исключений от `std::exception`, а еще лучше - от кого-то из |
||
| 1672 | классов-наследников, по типу `std::runtime_error` и т.п. |
||
| 1673 | |||
| 1674 | Желательно сперва проверить все предусловия функции и выполнить все возможные |
||
| 1675 | исключения, а затем исполнять основное тело функции. Это не всегда возможно и |
||
| 1676 | иногда ухудшает читаемость, но чаще всего - улучшает. |
||
| 1677 | |||
| 1678 | Убедитесь, что инварианты состояний классов не нарушаются при возникновении |
||
| 1679 | исключения, которое планируется обрабатывать. |
||
| 1680 | |||
| 1681 | Важно понимать, что исключительная ситуация - это такая ситуация, которую |
||
| 1682 | сущность не в состоянии парировать сама, и парирование которой выходит за рамки |
||
| 1683 | функциональных требований к программе. То есть, если решение о том, как |
||
| 1684 | действовать в случае возникновения той или иной ситуации выходит за рамки уровня |
||
| 1685 | абстракции текущего класса, то, вероятно, можно кидать исключения. |
||
| 1686 | |||
| 1687 | ### Ключевое слово `noexcept` |
||
| 1688 | |||
| 1689 | Используйте ключевое слово `noexcept`, если это полезно и корректно. |
||
| 1690 | |||
| 1691 | Спецификатор `noexcept` используется для того, чтобы указать, будет или нет |
||
| 1692 | функция генерировать исключения или нет. Если исключение выйдет за область |
||
| 1693 | видимости, программа вылетит через `std::terminate`. |
||
| 1694 | |||
| 1695 | Использование оператора `noexcept` приводит к проверкам времени компиляции, |
||
| 1696 | которые проходят, если выражение было разработано так, чтобы не генерировать |
||
| 1697 | никаких исключений. |
||
| 1698 | |||
| 1699 | * Спецификация конструктора перемещения как `noexcept` может повысить |
||
| 1700 | производительность в некоторых случаях. Например, `std::vector<T>::resize` |
||
| 1701 | использует семантику перемещения в том случае, если конструктор перемещения для |
||
| 1702 | типа `T` специфицирован как `noexcept`. |
||
| 1703 | * Если функция отмечена как `noexcept`, компилятор может применить |
||
| 1704 | дополнительную оптимизацию там, где включены исключения, поскольку не вставляет |
||
| 1705 | кода для раскрутки стека (*stack unwinding*) там, где не будут генерироваться |
||
| 1706 | исключения. |
||
| 1707 | |||
| 1708 | --- |
||
| 1709 | |||
| 1710 | * Тяжело в будущем убрать `noexcept` из функции, поскольку ее клиенты будут |
||
| 1711 | ожидать, что функция `noexcept` и появление исключения в неожиданном месте |
||
| 1712 | может быть сложным для обнаружения. |
||
| 1713 | |||
| 1714 | Использование `noexcept` допустимо в тех случаях, когда это полезно для |
||
| 1715 | повышения производительности, если это полностью согласуется с семантикой |
||
| 1716 | функции, то есть, должно быть нормально, что если в данной функции возникнет |
||
| 1717 | исключение - то это фатальная ошибка. Самый первый кандидат - конструктор |
||
| 1718 | перемещения. Если имеет смысл добавить спецификатор `noexcept` к какой-то |
||
| 1719 | другой функции, проконсультируйтесь с начальством. |
||
| 1720 | |||
| 1721 | Используйте безусловный `noexcept`, когда исключения невозможны. В противном |
||
| 1722 | случае используйте условные спецификаторы `noexcept` с простыми условиями, |
||
| 1723 | которые на выполняются только в очень редких случаях, когда функция может |
||
| 1724 | сгенерировать исключение. Можно, например, использовать проверки |
||
| 1725 | *особенностей типов (type traits)* или особые условия, когда какая-либо операция |
||
| 1726 | может сгенерировать исключение (`std::is_nothrow_move_constructable`), или |
||
| 1727 | когда выделение памяти может сгенерировать исключение |
||
| 1728 | (*absl::default_allocator_is_nothrow*). Скорее всего, если возникла ошибка |
||
| 1729 | аллокации или закончилась память, то это, скорее причина возникновения фатальной |
||
| 1730 | ошибки, чем исключения, от которого программа сможет оправиться. В любом случае, |
||
| 1731 | простота интерфейса приветствуется и, наверное, лучше написать `noexcept` без |
||
| 1732 | условий, чем описывать *очень сложные* условия внутри спецификатора `noexcept`, |
||
| 1733 | если есть причина сделать функцию `noexcept`. |
||
| 1734 | |||
| 1735 | ### RTTI |
||
| 1736 | |||
| 1737 | Избегайте использования RTTI. |
||
| 1738 | |||
| 1739 | *RTTI (runtime-type information)* - это механизм, позволяющий программисту |
||
| 1740 | запрашивать класс объекта во время исполнения. Это делается путем использования |
||
| 1741 | `typeid` или `dynamic_cast`. |
||
| 1742 | |||
| 1743 | Стандартные альтернативы RTTI (как описано ниже) требуют модификации или |
||
| 1744 | переделки дизайна иерархии классов. Иногда такие модификации сложны и |
||
| 1745 | нежелательны, особенно коде на поздних стадиях. |
||
| 1746 | |||
| 1747 | RTTI удобно использовать в некоторых модульных тестах. Например, тесты фабричных |
||
| 1748 | классов, в которых тест должен верифицировать, что у создаваемые объекты имеют |
||
| 1749 | нужный тип. Также это удобно при управлении отношением между объектами и их |
||
| 1750 | заглушками. |
||
| 1751 | |||
| 1752 | RTTI удобно использовать при работе с несколькими абстрактными объектами. |
||
| 1753 | Например: |
||
| 1754 | |||
| 1755 | ```c++ |
||
| 1756 | bool Base::Equal(Base* other) = 0; |
||
| 1757 | bool Derived::Equal(Base* other) { |
||
| 1758 | Derived* that = dynamic_cast<Derived*>(other); |
||
| 1759 | if(that == nullptr) |
||
| 1760 | return false; |
||
| 1761 | ... |
||
| 1762 | } |
||
| 1763 | ``` |
||
| 1764 | |||
| 1765 | Сам по себе запрос типа объекта во время исполнения - это сигнал о потенциальных |
||
| 1766 | проблемах в проектировании. Часто необходимость в использовании RTTI говорит о |
||
| 1767 | том, что в иерархии объектов есть изъяны. |
||
| 1768 | |||
| 1769 | Бесконтрольное использование RTTI ведет к тому, что код становится трудно |
||
| 1770 | поддерживать. Это ведет к деревьям решений, основанным на типах данных, которые |
||
| 1771 | будут рассыпаны по всему коду, которые придется изучать при внесении изменений |
||
| 1772 | в код. |
||
| 1773 | |||
| 1774 | Использование RTTI допустимо, но может легко привести к злоупотреблению, поэтому |
||
| 1775 | применять следует с осторожностью. Использование в модульных тестах допускается |
||
| 1776 | без ограничений, но в коде его следует максимально избегать. В частности, |
||
| 1777 | подумайте дважды перед использованием RTTI в новом коде. Если вам необходимо |
||
| 1778 | написать код, который ведет себя в зависимости от класса объекта, попробуйте |
||
| 1779 | рассмотреть следующие альтернативы: |
||
| 1780 | * Виртуальные методы - это предпочтительный способ исполнения различных частей |
||
| 1781 | кода в зависимости от специфического типа подкласса. Тогда вся работа будет |
||
| 1782 | сделана внутри объекта. |
||
| 1783 | * Если код находится вне объекта, то можно использовать механизмы |
||
| 1784 | множественной диспетчеризации. Например, шаблон проектирования Visitor. |
||
| 1785 | |||
| 1786 | Если логика программы гарантирует, что объект базового класса - это фактически |
||
| 1787 | объект определенного класса-наследника, то можно свободно использовать |
||
| 1788 | `dynamic_cast`. Часто в подобных ситуациях можно использовать `static_cast`. |
||
| 1789 | |||
| 1790 | Деревья решений, основанные на типах - это верный признак того, что разработка |
||
| 1791 | движется не в том направлении |
||
| 1792 | |||
| 1793 | ```c++ |
||
| 1794 | if(typeid(*data) == typeid(D1)) { |
||
| 1795 | ... |
||
| 1796 | } |
||
| 1797 | else if(typeid(*data) == typeid(D2)) { |
||
| 1798 | ... |
||
| 1799 | } |
||
| 1800 | else if(typeid(*data) == typeid(D3)) { |
||
| 1801 | ... |
||
| 1802 | ``` |
||
| 1803 | |||
| 1804 | Подобный код обычно ломается при добавлении нового класса. Более того, когда |
||
| 1805 | свойства уже имеющихся классов меняются, сложно найти и модифицировать все |
||
| 1806 | связанные сегменты кода. |
||
| 1807 | |||
| 1808 | > Не стоит изобретать собственный аналог RTTI. Как правило, минусы RTTI будут |
||
| 1809 | > касаться и этих аналогов. |
||
| 1810 | |||
| 1811 | ### Приведение типов |
||
| 1812 | |||
| 1813 | Используйте приведение типов в стиле C++, например, |
||
| 1814 | `static_cast<float>(double_value)` или инициализацию с помощью фигурных скобок |
||
| 1815 | для арифметических типов, например `int64_t y = int64_t{1} << 42`. Не |
||
| 1816 | используйте приведение типов формата C, например `(int) x`, кроме случаев |
||
| 1817 | приведения к типу `void`. Используйте формат `T(x)` только если `T` - класс. |
||
| 1818 | |||
| 1819 | В C++ присутствует отличная от C система приведения типов, которая разделяет |
||
| 1820 | различные операции приведения. |
||
| 1821 | |||
| 1822 | Главная проблема с приведением в C - неоднозначность, иногда происходит |
||
| 1823 | конверсия типов `(int)3.5`, иногда приведение, например `(int)"hello"`. |
||
| 1824 | Инициализация в фигурных скобках и приведение типов из C++ помогают избежать |
||
| 1825 | этой неоднозначности. Также это приведение легко найти в коде поиском или |
||
| 1826 | благодаря подсветке синтаксиса. |
||
| 1827 | |||
| 1828 | Приведение типов на C++ многословно и громоздко. |
||
| 1829 | |||
| 1830 | В общем, не используйте приведение в стиле C. Вместо этого используйте |
||
| 1831 | приведением в стиле C++, если требуется явное преобразование типов. |
||
| 1832 | |||
| 1833 | * Используйте инициализацию в фигурных скобках для арифметических типов. Это |
||
| 1834 | самый безопасный способ, поскольку код не компилируется в том случае, если |
||
| 1835 | возможна потеря информации. Синтаксис в таком случае выйдет довольно лаконичным. |
||
| 1836 | * Используйте `absl::implicit_cast` для безопасного приведения типов вверх по |
||
| 1837 | иерархии классов. Например для приведения `Foo*` к `SuperclassOfFoo` или `const |
||
| 1838 | Foo*`. Как правило, это делается автоматически компилятором, но иногда нужно |
||
| 1839 | делать явно, например, для оператора `?:`. |
||
| 1840 | * Используйте `static_cast` как эквивалент приведения типов из C, которое |
||
| 1841 | конвертирует значение или при необходимости явно преобразовать класс к |
||
| 1842 | классу-наследнику. В этом случае вы должны быть уверены, что объект на самом |
||
| 1843 | деле является объектом класса-наследника. |
||
| 1844 | * Используйте `const_cast` для того, чтобы убрать модификатор |
||
| 1845 | [const](#ключевое-слово-const). |
||
| 1846 | * Используйте `reinterpret_cast` для небезопасного приведения указателей, |
||
| 1847 | включая `void*`. Используйте это только когда знаете, что делаете. Рассмотрите |
||
| 1848 | `absl::bit_cast` в качестве альтернативы. |
||
| 1849 | * Используйте `absl::bit_cast` для приведения "сырых" битов в другой тип того |
||
| 1850 | же размера, например, `double` в `int64_t`. |
||
| 1851 | |||
| 1852 | В [секции бо RTTI](#RTTI) описано использование `dynamic_cast`. |
||
| 1853 | |||
| 1854 | ### Потоки ввода-вывода |
||
| 1855 | |||
| 1856 | Используйте потоковый ввод-вывод когда необходимо. Перегружайте операцию `<<` |
||
| 1857 | только для типов, которые представляют собой данные и специализируйте только |
||
| 1858 | значения, доступные для пользователя, без деталей реализации. |
||
| 1859 | |||
| 1860 | Потоки - это стандартный способ ввода/вывода в C++. Они широко используются для |
||
| 1861 | логирования и тестовой диагностики. |
||
| 1862 | |||
| 1863 | Потоки предоставляют простой в освоении API для форматированного ввода-вывода, |
||
| 1864 | который легко портировать и повторно использовать. Для сравнения, например, |
||
| 1865 | `printf` не поддерживает `std::string`, не говоря уже о пользовательских типах |
||
| 1866 | данных. Также сложно разрабатывать портируемый код, использующий `printf`. |
||
| 1867 | Кроме того, printf вынуждает выбирать среди похожих версий одной функции и |
||
| 1868 | ориентироваться в десятках форматных символах. |
||
| 1869 | |||
| 1870 | Потоки обеспечивают хорошую поддержку консольного ввода-вывода через `std::cin`, |
||
| 1871 | `std::cout`, `std::cerr`, `std::clog`. Функции из C API тоже неплохо работают, |
||
| 1872 | но могут требовать ручной буферизации ввода. |
||
| 1873 | |||
| 1874 | --- |
||
| 1875 | |||
| 1876 | * Форматирование потоков может изменять состояние потока. Это состояние будет |
||
| 1877 | постоянным и влиять на весь последующий ввод-вывод до тех пор, пока вы не |
||
| 1878 | будете возвращаться к предыдущему состоянию каждый раз. Более того, |
||
| 1879 | пользовательский код может не только модифицировать уже имеющееся состояние, |
||
| 1880 | но и вводить новые. |
||
| 1881 | * Поскольку код и данные перемешаны во время вывода потока, то сложно |
||
| 1882 | контролировать, что и как именно будет выведено в поток. |
||
| 1883 | * Формирование вывода посредством вызова цепочки операторов `<<` затрудняет |
||
| 1884 | локализацию, т.к. при этом жёстко фиксируется порядок слов. |
||
| 1885 | * API потоков сложен в освоении, поэтому программисты должны обладать большим |
||
| 1886 | опытом для его грамотного использования. |
||
| 1887 | * Выборка нужной перегрузки оператора `<<` - затратная для компилятора |
||
| 1888 | операция. |
||
| 1889 | |||
| 1890 | Используйте потоковый ввод-вывод только если это лучшее решение проблемы. |
||
| 1891 | Библиотеки для логирования часто лучший выход, чем `std::cerr` или `std::clog`, |
||
| 1892 | а библиотеки для работы со строками лучше, чем `std::stringstream`. Если код |
||
| 1893 | использует Qt, то лучше всего использовать отладочные потоки по типу `qDebug` и |
||
| 1894 | `QString`, соответственно. Если нет - найдите подходящую легковесную библиотеку. |
||
| 1895 | |||
| 1896 | Не используйте потоки для ввода-вывода данных для конечного пользователя или |
||
| 1897 | там, где нет доверия к данным. Найдите подходящую библиотеку, которая |
||
| 1898 | поддерживает локализацию, поддержку защиты информации и т. п. |
||
| 1899 | |||
| 1900 | При использовании потоков избегайте API, которые меняют состояние потоков, |
||
| 1901 | например `imbue()`, `xalloc()` или `register_callback()`. Используйте явные |
||
| 1902 | функции форматирования, например, `absl::StreamFormat()` вместо потоковых |
||
| 1903 | манипуляторов. |
||
| 1904 | |||
| 1905 | Перегружайте оператор `<<` только для типов, если тип представляет собой |
||
| 1906 | значение и необходимо выводит человекочитаемые данные. Избегайте вывода в |
||
| 1907 | поток деталей реализации. Если нужно выдать отладочную информацию, используйте |
||
| 1908 | обычные функции или методы. Например, добавьте в класс метод `debugString()`, |
||
| 1909 | который вернет такую информацию в виде `std::string`. |
||
| 1910 | |||
| 1911 | ### Преинкремент и предекремент |
||
| 1912 | |||
| 1913 | Используйте префиксные формы (`++i`) за исключением тех случаев, когда вам явно |
||
| 1914 | необходима постфиксная семантика. |
||
| 1915 | |||
| 1916 | Когда переменная инкрементируется (`++i`/`i++`) или декрементируется |
||
| 1917 | (`--i`/`i--`) и значение выражения не используется, необходимо четко решать, |
||
| 1918 | какой вид инкремента/декремента используется. |
||
| 1919 | |||
| 1920 | Префиксные операции эффективнее в тех случаях, когда значение выражения |
||
| 1921 | игнорируется, поскольку они не требуют создания копии переменной. Помимо этого, |
||
| 1922 | постфиксная операция труднее читается. |
||
| 1923 | |||
| 1924 | Раньше везде использовали постфиксный формат в циклах, поэтому он привычнее. |
||
| 1925 | |||
| 1926 | Используйте префиксный формат (`++i`/`--i`) во всех случаях, когда коду не |
||
| 1927 | нужна именно постфиксная семантика. |
||
| 1928 | |||
| 1929 | ### Ключевое слово `const` |
||
| 1930 | |||
| 1931 | Используйте `const` везде, где это имеет смысл. В некоторых случаях |
||
| 1932 | `constexpr` - лучший вариант. |
||
| 1933 | |||
| 1934 | При объявлении переменных или параметров можно указать ключевое слово `const` |
||
| 1935 | для обозначения того, что значение переменной не будет меняться |
||
| 1936 | (`const int foo`). Функции-члены классов могут иметь квалификатор `const`, |
||
| 1937 | чтобы обозначить, что функция не меняет состояния переменных класса |
||
| 1938 | (`class Foo { int Bar(char c) const; };`). |
||
| 1939 | |||
| 1940 | В таком случае читателю будет понятнее, как именно переменная или функция будет |
||
| 1941 | использоваться. Также это позволяет компилятору проводить оптимизацию и |
||
| 1942 | генерировать более производительный код. Также `const` повышает надежность |
||
| 1943 | программы, поскольку явно декларирует, будут ли данные меняться или нет. |
||
| 1944 | Позволяет пользователям понимать, что функции безопасны для использования без |
||
| 1945 | блокировок в многопоточных программах. |
||
| 1946 | |||
| 1947 | Использование `const` - очень заразное. Если в функцию передается |
||
| 1948 | `const`-переменная, то в прототипе также должен стоять `const` или потребуется |
||
| 1949 | `const_cast`. Это может затруднить использование библиотечных функций. |
||
| 1950 | |||
| 1951 | Ключевое слово `const` настоятельно рекомендуется к использованию в API |
||
| 1952 | (например, в параметрах функций, методах, нелокальных переменных) везде, где это |
||
| 1953 | осмысленно и точно. Это делает предоставляет целостное и верифицированное |
||
| 1954 | компилятором описание, какие объекты будут изменяться при вызове. Четкое и |
||
| 1955 | надежное разделение чтения и записи критично для разработки потокобезопасного |
||
| 1956 | кода и полезно во многих других случаях. В частности: |
||
| 1957 | * Если функция не меняет значение аргумента, передаваемого по значению или по |
||
| 1958 | указателю, соответствующий параметр должен быть ссылкой или указателем на |
||
| 1959 | константу (`const T&`/`const T*`). |
||
| 1960 | * Если параметр передается по значению, использование `const` не имеет эффекта |
||
| 1961 | и не рекомендуется. |
||
| 1962 | * Объявляйте методы как `const`, если они не меняют значения объекта (и не дают |
||
| 1963 | такой возможности, например, возвращая неконстантную ссылку) и могут безопасно |
||
| 1964 | вызываться из нескольких потоков. |
||
| 1965 | |||
| 1966 | Использование `const` для локальных переменных остается на усмотрение |
||
| 1967 | программиста. |
||
| 1968 | |||
| 1969 | Все `const`-методы класса должны иметь возможность вызываться одновременно из |
||
| 1970 | разных потоков. В противном случае класс должен быть явно задокументирован как |
||
| 1971 | небезопасный. |
||
| 1972 | |||
| 1973 | #### Где использовать `const` |
||
| 1974 | |||
| 1975 | Некоторым людям нравится `int const* foo` вместо `const int* foo`. Так делать не |
||
| 1976 | надо, `const int* foo` звучит как словосочетание на английском языке. |
||
| 1977 | |||
| 1978 | ### Ключевое слово `constexpr` |
||
| 1979 | |||
| 1980 | Используйте `constexpr`, чтобы определять истинные константы времени компиляции |
||
| 1981 | или чтобы гарантировать константную инициализацию. |
||
| 1982 | |||
| 1983 | Некоторые переменные могут быть объявлены как `constexpr`, чтобы обозначит |
||
| 1984 | истинные константы, т.е., константы времени компиляции или компоновки. Некоторые |
||
| 1985 | функции или конструкторы могут быть объявлены как `constexpr`, чтобы обозначить, |
||
| 1986 | что они могут быть использованы в `constexpr`-выражениях. |
||
| 1987 | |||
| 1988 | Использование `constexpr` позволяет, например, создать константу в виде |
||
| 1989 | выражения с плавающей запятой, которое будет вычисляться во время компиляции |
||
| 1990 | вместо использования литералов, а также использовать в этих выражениях вызовы |
||
| 1991 | функций. |
||
| 1992 | |||
| 1993 | Если помечать что-то как `constexpr` слишком рано, то могут возникнуть |
||
| 1994 | проблемы с миграцией, если позже будет необходимо убрать `constexpr`. |
||
| 1995 | Ограничения, что можно и что нельзя использовать в `constexpr`-функциях или |
||
| 1996 | конструкторах могут привести к использованию неочевидных обходных путей. |
||
| 1997 | |||
| 1998 | Самый надежный способ объявить константу в интерфейсе - это `constexpr`. |
||
| 1999 | Используйте `constexpr`, чтобы определять истинные константы и функции, которые |
||
| 2000 | можно для них использовать. Не усложняйте функцию для использования с |
||
| 2001 | `constexpr`. Не используйте `constexpr`, чтобы заставить компилятор делать |
||
| 2002 | функции встроенными (inline). |
||
| 2003 | |||
| 2004 | ### Целочисленные типы данных |
||
| 2005 | |||
| 2006 | Из встроенных целочисленных типов данных C++ следует использовать только `int`. |
||
| 2007 | Для переменных других типов используйте целочисленные типы данных с точными |
||
| 2008 | размерами из `<cstdint>`, например, `int16_t`. Для работы с системой |
||
| 2009 | метаобъектов из библиотеки Qt допустимо использование типов данных из |
||
| 2010 | заголовочного файла `<QtGlobal>`. Держите в уме, что даже если для результата |
||
| 2011 | требуется меньший размер типа данных, промежуточный результат вычислений может |
||
| 2012 | требовать типа данных побольше. Если сомневаетесь, используйте тип данных |
||
| 2013 | побольше. |
||
| 2014 | |||
| 2015 | C++ не уточняет размер целочисленных типов, таких как `int`. Обычно люди |
||
| 2016 | предполагают, что `short` содержит 16 битов, `int` — 32, `long` — 32 и `long |
||
| 2017 | long` содержит 64 бита. |
||
| 2018 | |||
| 2019 | Размер целочисленных типов зависит от аппаратной архитектуры. |
||
| 2020 | |||
| 2021 | В стандартном заголовочном файле `<cstdint>` определены типы, такие как: |
||
| 2022 | `int16_t`, `uint32_t`, `int64_t` и т.д. Не используйте `short`, `unsigned long`, |
||
| 2023 | `long`, чтобы быть уверенными в размере типа данных. Из целочисленных типов |
||
| 2024 | языка C можно использовать только `int`. Также, в соответствующих случаях, |
||
| 2025 | используйте `size_t` и `ptrdiff_t`. Тип `int` используется очень часто, особенно |
||
| 2026 | для небольших значений, например как счётчики в циклах. Можете считать, что |
||
| 2027 | `int` содержит минимум 32 бита (но не больше). Если требуется 64 битный |
||
| 2028 | целочисленный тип, то следует использовать `int64_t` или `uint64_t`. |
||
| 2029 | |||
| 2030 | Для целых чисел, которые могут быть *большими*, используйте `int64_t`. |
||
| 2031 | |||
| 2032 | Не стоит использовать беззнаковые числа (например, `uint32_t`). Допустимое |
||
| 2033 | применение беззнаковых чисел это использование битовых представлений или |
||
| 2034 | использование переполнения (по модулю `2^N`) в расчётах. В частности, не |
||
| 2035 | используйте беззнаковый тип для того, чтобы убедиться, что число всегда будет |
||
| 2036 | положительным. Используйте `assert`, чтобы это гарантировать. |
||
| 2037 | |||
| 2038 | Если вы разрабатываете контейнер, который возвращает размер, убедитесь, что типа |
||
| 2039 | данных хватит для любого потенциально возможного использования контейнера. Если |
||
| 2040 | сомневаетесь - используйте тип данных большего размера. |
||
| 2041 | |||
| 2042 | Будьте внимательны при конвертировании целочисленных типов. Может проявится |
||
| 2043 | *неопределённое поведение (undefined behavior)*, ведущее к ошибкам |
||
| 2044 | безопасности и другим проблемам. |
||
| 2045 | |||
| 2046 | #### Беззнаковые целочисленные типы |
||
| 2047 | |||
| 2048 | Беззнаковые целые числа хороши для представления битовых полей и модульной |
||
| 2049 | арифметики. Исторически сложилось, что в стандарте C++ для размеров контейнеров |
||
| 2050 | используется беззнаковый тип. Многие из разработчиков стандарта признают, |
||
| 2051 | что это была ошибка, но в данный момент это невозможно эффективно исправить. |
||
| 2052 | Беззнаковая арифметика отличается от простой целочисленной, а соответствует |
||
| 2053 | стандартам *модульной арифметики*, то есть переходят через 0 при переполнении. |
||
| 2054 | Это приводит к ошибкам, которые невозможно выявить компилятором. Также, это |
||
| 2055 | затрудняет оптимизацию. |
||
| 2056 | |||
| 2057 | Смешивание знаковых и беззнаковых целочисленных типов приводит к такому же |
||
| 2058 | количеству проблем. Лучший совет: старайтесь использовать итераторы вместо |
||
| 2059 | указателей и размеров, старайтесь не смешивать знаковые и беззнаковые типы |
||
| 2060 | данных и старайтесь избегать беззнаковых типов за исключением битовых полей и |
||
| 2061 | тех случаев, где явно нужна модульная арифметика. Не используйте беззнаковый тип |
||
| 2062 | только для того, чтобы гарантировать, что переменная неотрицательна. |
||
| 2063 | |||
| 2064 | ### Переносимость на 64-битные системы |
||
| 2065 | |||
| 2066 | Код должен одинаково хорошо работать как с 64-битной, так и с 32-битной |
||
| 2067 | архитектурой. Держите в уме проблемы печати, сравнений и выравнивания структур. |
||
| 2068 | |||
| 2069 | * Переносимое использование `printf` предполагает использование неприятных и |
||
| 2070 | непрактичных макросов (`PRI`-макросы из `<cinttypes>`). Не используйте `API`, |
||
| 2071 | которые зависят от семейства `printf`. Вместо этого используйте безопасное |
||
| 2072 | целочисленное форматирование, например `StrCat` или `Substitute` из библиотеки |
||
| 2073 | `absl` или `std::ostream`. |
||
| 2074 | > К сожалению, `PRI`-макросы - это единственный переносимый способ, чтобы |
||
| 2075 | > указать в `printf` форматирование для целочисленных типов данных с |
||
| 2076 | > точными размерами (например, `int64_t, uint64_t, int32_t, uint32_t` |
||
| 2077 | > и т.д.). Избегайте использование `printf`. Если невозможно, используйте |
||
| 2078 | > типы данных, для которых у `printf` есть выделенный формат, например |
||
| 2079 | > `size_t(z)`, `ptrdiff_t(t)`, `maxint_t(j)`. |
||
| 2080 | * Помните, что `sizeof(void*) != sizeof(int)`. Используйте `intptr_t`, если вым необходим знаковый тип такой же размерности, что и указатель. |
||
| 2081 | * Будьте осторожны с выравнивание структур, в частности при сериализации на |
||
| 2082 | диск. Класс/структура, в которых есть член типа `int64_t` или `uint64_t` |
||
| 2083 | по-умолчанию имеют выравнивание по границе 8 байт в 64-битных системах. При |
||
| 2084 | работе со структурами, которые сохраняются на диске и используются 32-битным и |
||
| 2085 | 64-битным кодом, необходимо убедиться, что данные выровнены одинаково. В gcc |
||
| 2086 | можно использовать `__attribute__((packed))`. В MSVC имеется `#pragma pack()` |
||
| 2087 | и `__declspec(align())`. |
||
| 2088 | * Используйте инициализацию в фигурных скобках, если требуется создать |
||
| 2089 | 64-битную константу. Например: |
||
| 2090 | |||
| 2091 | ```c++ |
||
| 2092 | int64_t my_value{0x123456789}; |
||
| 2093 | uint64_t my_mask{3ULL << 48}; |
||
| 2094 | ``` |
||
| 2095 | |||
| 2096 | ### Макросы препроцессора |
||
| 2097 | |||
| 2098 | Избегайте определения макросов, особенно в заголовочных файлах. Вместо этого |
||
| 2099 | используйте встраиваемые функции, перечисления или переменные-константы. Если |
||
| 2100 | используете макросы, то в имени используйте префикс—название проекта. Не |
||
| 2101 | используйте макросы, чтобы переопределить или дополнить C++ API. |
||
| 2102 | |||
| 2103 | Макросы подразумевают, что код, который в видите - не тот код, который выдается |
||
| 2104 | на вход компилятору. Это может вызвать неожиданное поведение, особенно если |
||
| 2105 | макросы имеют глобальную область видимости. |
||
| 2106 | |||
| 2107 | Проблемы, связанные с макросами особенно усугубляются, когда они используются |
||
| 2108 | для определения фрагментов API C++. При любых ошибках в использовании API |
||
| 2109 | потребуется разбираться в логике макросов; увеличивается время разбора код |
||
| 2110 | инструментами рефакторинга или анализаторами. Как результат, использование |
||
| 2111 | макросов в таких случаях запрещено. Например, откажитесь от подобного кода: |
||
| 2112 | |||
| 2113 | ```c++ |
||
| 2114 | class WOMBAT_TYPE(Foo) { |
||
| 2115 | // ... |
||
| 2116 | public: |
||
| 2117 | EXPAND_PUBLIC_WOMBAT_API(Foo) |
||
| 2118 | EXPAND_WOMBAT_COMPARISONS(Foo, ==, <) |
||
| 2119 | }; |
||
| 2120 | ``` |
||
| 2121 | |||
| 2122 | К счастью, макросы не так необходимы в C++, как в языке C. Вместо макросов для |
||
| 2123 | высокопроизводительного кода можно использовать встраиваемые функции. Вместо |
||
| 2124 | макросов, которые хранят константы, используйте `const`. Вместо использования |
||
| 2125 | макросов для укорачивания длинных названий переменной используйте ссылки. |
||
| 2126 | Старайтесь не использовать условную компиляцию, это усложняет тестирование. |
||
| 2127 | |||
| 2128 | Макросы позволяют делать вещи, которые невозможно сделать без них и вы будете |
||
| 2129 | их видеть и использовать, особенно в низкоуровневом коде. Некоторые специальные |
||
| 2130 | приемы, (такие как преобразование в строку, конкатенация и т.п.) невозможно |
||
| 2131 | провернуть без их использования. Но прежде, чем использовать макрос, попробуйте |
||
| 2132 | найти способ достичь того же результата по-другому. Если же вам нужен макрос для |
||
| 2133 | определения интерфейса, проконсультируйтесь с руководством. |
||
| 2134 | |||
| 2135 | Следующие правила помогут избежать многих проблем, связанных с макросами. |
||
| 2136 | Следуйте им когда это возможно: |
||
| 2137 | * Не определяйте макрос в заголовочном файле. |
||
| 2138 | * Определяйте макросы (`#define`) как можно ближе к месту первого |
||
| 2139 | использования. Удаляйте (`#undef`) сразу после последнего. |
||
| 2140 | * Не делайте `#undef` существующих макросов для того, чтобы определить новый с |
||
| 2141 | таким же названием. Вместо этого придумайте уникальное название для своего |
||
| 2142 | макроса. |
||
| 2143 | * Старайтесь не использовать макросы, которые при раскрытии превращаются в |
||
| 2144 | большие, несбалансированные конструкции на C++. По крайней мере, как следует |
||
| 2145 | их документируйте. |
||
| 2146 | * Старайтесь не использовать препроцессорную конкатенацию (`##`) для того, |
||
| 2147 | чтобы сгенерировать название функции/класса/переменной. |
||
| 2148 | |||
| 2149 | Настоятельно не рекомендуется экспортировать макросы из заголовочных файлов |
||
| 2150 | (т.е. определять макрос и не удалять его в конце заголовочного файла). Если |
||
| 2151 | макрос экспортируется из заголовочного файла, то его название должно быть |
||
| 2152 | глобально-уникальным. Как вариант, добавьте префикс с пространством имён |
||
| 2153 | проекта (заглавными буквами). |
||
| 2154 | |||
| 2155 | Макросы бывают необходимы при определении заголовочных файлов разделяемых |
||
| 2156 | библиотек в ОС Windows. Например |
||
| 2157 | |||
| 2158 | ``` c++ |
||
| 2159 | #ifdef MY_LIBRARY |
||
| 2160 | #define MY_LIBRARY_PUBLIC_API __declspec(dllexport) |
||
| 2161 | #else |
||
| 2162 | #define MY_LIBRARY_PUBLIC_API __declspec(dllimport) |
||
| 2163 | #endif |
||
| 2164 | ``` |
||
| 2165 | |||
| 2166 | ### `0`, `NULL` и `nullptr` |
||
| 2167 | |||
| 2168 | Используйте `nullptr` для нулевых указателей и `\0` для символа конца строки. |
||
| 2169 | |||
| 2170 | Использование `nullptr` для указателей улучшает безопасность типов. |
||
| 2171 | |||
| 2172 | Использование `\0` для символа конца строки делает код более читаемым. |
||
| 2173 | |||
| 2174 | ### sizeof |
||
| 2175 | |||
| 2176 | Использование `sizeof(переменная)` вместо `sizeof(тип)` предпочтительнее. |
||
| 2177 | |||
| 2178 | Используйте `sizeof(переменная)` тогда, когда вам необходимо узнать размер |
||
| 2179 | переменной. Если тип переменной изменится, то и значение `sizeof(переменная)` |
||
| 2180 | обновится. Используйте `sizeof(тип)` в тех случаях, когда код не работает с |
||
| 2181 | конкретной переменной. |
||
| 2182 | |||
| 2183 | ``` c++ |
||
| 2184 | MyStruct data; |
||
| 2185 | memset(&data, 0, sizeof(data)); |
||
| 2186 | memset(&data, 0, sizeof(MyStruct)); // Плохо |
||
| 2187 | ``` |
||
| 2188 | |||
| 2189 | ``` c++ |
||
| 2190 | if(raw_size < sizeof(int)) { |
||
| 2191 | LOG(ERROR) << "compressed record not big enough for count: " << raw_size; |
||
| 2192 | return false; |
||
| 2193 | } |
||
| 2194 | ``` |
||
| 2195 | |||
| 2196 | ### Вывод типов данных (в том числе `auto`) |
||
| 2197 | |||
| 2198 | Используйте *вывод типов данных (type deduction)* только в том случае, когда |
||
| 2199 | это сделает код понятнее для людей, не знакомых с проектом или если это делает |
||
| 2200 | код безопаснее. |
||
| 2201 | Не используйте его только для того, чтобы избежать неудобства от явного описания |
||
| 2202 | типа. |
||
| 2203 | |||
| 2204 | Существует несколько ситуаций, в которых C++ позволяет (или даже требует), |
||
| 2205 | чтобы тип данных был выведен компилятором вместо того, чтобы быть явно указанным |
||
| 2206 | в коде. |
||
| 2207 | |||
| 2208 | _Вывод типов данных в шаблонах функций_ |
||
| 2209 | |||
| 2210 | > Шаблон функции может быть вызван без явного указания параметров шаблона. Компилятор выводит их из типов аргументов функции: |
||
| 2211 | |||
| 2212 | > ```c++ |
||
| 2213 | > template <typename T> |
||
| 2214 | > void f(T t); |
||
| 2215 | > |
||
| 2216 | >f(0); // Вызывается f<int>(0) |
||
| 2217 | > ``` |
||
| 2218 | |||
| 2219 | _Переменные с типом `auto`_: |
||
| 2220 | |||
| 2221 | > Объявление переменной может использовать ключевое слово `auto` вместо типа |
||
| 2222 | > данных. Компилятор определяет тип из выражения инициализации, следуя |
||
| 2223 | > правилам, аналогичным для шаблонной функции, пока не используются фигурные |
||
| 2224 | > скобки: |
||
| 2225 | |||
| 2226 | > ```c++ |
||
| 2227 | > auto a = 42; // a типа int |
||
| 2228 | > auto& b = a; // b типа int& |
||
| 2229 | > auto c = b; // c типа int |
||
| 2230 | > auto d{42}; // d типа int, а не std::initializer_list<int> |
||
| 2231 | > ``` |
||
| 2232 | > Ключевое слово `auto` может быть использовано с квалификатором `const` или |
||
| 2233 | > как часть ссылки или указателя, но его нельзя использовать как аргумент |
||
| 2234 | > шаблона. Более редкий способ использования: |
||
| 2235 | > `decltype(auto)`, когда тип выводится из применения `decltype` к |
||
| 2236 | > выражению инициализации. |
||
| 2237 | |||
| 2238 | _Вывод типа возвращаемого значения функции_ |
||
| 2239 | > Ключевое слово `auto` может применяться также и вместо типа возвращаемого |
||
| 2240 | > значения функции. В таком случае компилятор определит тип возвращаемого |
||
| 2241 | > значения из тела функции, следуя правилам для определения типов переменных: |
||
| 2242 | |||
| 2243 | > ``` c++ |
||
| 2244 | > auto f() { return 0; } // Возвращаемый f тип - int |
||
| 2245 | > ``` |
||
| 2246 | |||
| 2247 | > Возвращаемый тип лямбда-функции также выводится подобным образом, но это |
||
| 2248 | > делается путем опускания типа возвращаемого значения, без использования |
||
| 2249 | > `auto`. Запутывает, что для |
||
| 2250 | > [указания возвращаемого типа в конце](#новый-синтаксис-возвращаемых-значений) |
||
| 2251 | > функции также необходимо использовать `auto`. |
||
| 2252 | |||
| 2253 | _Обобщенные (generic) лямбда-функции_ |
||
| 2254 | > Лямбда-выражение может использовать ключевое слово `auto` вместо типа одного |
||
| 2255 | > или нескольких типов параметров. В таком случае лямбда-функция становится |
||
| 2256 | > шаблоном функции со своим шаблонным параметром для каждого параметра функции, |
||
| 2257 | > для которого вместо типа указано `auto`: |
||
| 2258 | |||
| 2259 | > ``` c++ |
||
| 2260 | > // Сортируем `vec` по возрастанию |
||
| 2261 | > std::sort(vec.begin(), vec.end(), [](auto lhs, auto rhs) { return lhs > rhs; }); |
||
| 2262 | > ``` |
||
| 2263 | |||
| 2264 | _Инициализация переменных захвата лямбда-функции_ |
||
| 2265 | > В секции захвата лямбда-функции новые переменные, инициализированные |
||
| 2266 | > значениями: |
||
| 2267 | > |
||
| 2268 | > ``` c++ |
||
| 2269 | > [x = 42, y = "foo"] { ... } // тип x - int, y - const char* |
||
| 2270 | > ``` |
||
| 2271 | > |
||
| 2272 | > Синтаксис не позволяет указать тип новой переменной, он выводится по тем же |
||
| 2273 | > правилам, что и `auto`-переменные. |
||
| 2274 | |||
| 2275 | _Вывод типа аргумента шаблона класса_ |
||
| 2276 | > См. [соответствующий раздел](#вывод-аргументов-шаблонов-классов). |
||
| 2277 | |||
| 2278 | _Структурные привязки_ |
||
| 2279 | > При объявлении кортежей, структур, или массивов с использованием ключевого |
||
| 2280 | > слова `auto` можно указать названия отдельных элементов вместо названия |
||
| 2281 | > целого объекта. Это называется *структурная привязка (structured binding)*, а |
||
| 2282 | > сам объявление называется *объявлением структурной привязки* |
||
| 2283 | > *(structured binding declaration)*. Синтаксис не позволяет задать тип ни |
||
| 2284 | > полного объекта, ни отдельных имён: |
||
| 2285 | > |
||
| 2286 | > ``` c++ |
||
| 2287 | > auto [iter, success] = my_map.insert({key, value}); |
||
| 2288 | > if(!success) { |
||
| 2289 | > iter->second = value; |
||
| 2290 | > } |
||
| 2291 | > ``` |
||
| 2292 | > |
||
| 2293 | > Ключевое слово `auto` может быть квалифицировано как `const`, `&`, `&&`, |
||
| 2294 | > но заметим, что эти квалификаторы применяются ко всему анонимному массиву/ |
||
| 2295 | > кортежу/структуре, а не к отдельным привязкам. Правила определения конечного |
||
| 2296 | > типа привязок довольно сложны, но результат обычно предсказуем. Можно только |
||
| 2297 | > отметить, что тип привязки обычно не может быть ссылочным, даже если в |
||
| 2298 | > декларации указана ссылка (хотя поведение всё равно может быть как у ссылки). |
||
| 2299 | |||
| 2300 | Приведенная выше информация опускает некоторые детали, поэтому изучите вопрос |
||
| 2301 | самостоятельно. |
||
| 2302 | |||
| 2303 | * Названия типов данных в C++ могут быть длинными и громоздкими, особенно при |
||
| 2304 | использовании шаблонов или пространств имен. |
||
| 2305 | * Когда название одного и того же типа данных повторяется много раз внутри |
||
| 2306 | небольшого участка кода, это не улучшает читаемость. |
||
| 2307 | * Иногда вывод типа данных безопаснее, поскольку позволяет избегать случайного |
||
| 2308 | копирования или преобразования типов. |
||
| 2309 | |||
| 2310 | Как правило код на C++ яснее, когда все типы данных указываются явно, особенно |
||
| 2311 | если вывод типов опирается на информацию, указанную в отдаленной части кода. В |
||
| 2312 | выражениях вроде: |
||
| 2313 | |||
| 2314 | ``` c++ |
||
| 2315 | auto foo = x.add_foo(); |
||
| 2316 | auto i = y.Find(key); |
||
| 2317 | ``` |
||
| 2318 | |||
| 2319 | может быть непонятно, какой тип данных будет выведен, особенно тип переменной |
||
| 2320 | `y` не очень популярен или если она была объявлена много строк назад. |
||
| 2321 | |||
| 2322 | Программистам необходимо понимать, когда вывод типов данных будет или нет |
||
| 2323 | ссылкой или в неожиданном месте может случиться копирование. |
||
| 2324 | |||
| 2325 | Если выводимые типы используются как часть интерфейса, программисты могут |
||
| 2326 | изменить этот тип при попытке изменить значение, что приведет к радикальным |
||
| 2327 | изменениям в API. |
||
| 2328 | |||
| 2329 | _Фундаментальное правило таково_: Используйте вывод типов данных только для |
||
| 2330 | того, чтобы сделать код чище и понятнее. Не используйте его только для того, |
||
| 2331 | чтобы избежать неудобства от явного описания типа. Рассуждая о том, будет ли код |
||
| 2332 | чище и понятнее, стоит держать в уме, что читатели кода могут относиться к |
||
| 2333 | другому подразделению или не быть знакомыми с вашим проектом. Если явное |
||
| 2334 | указание типов данных может показаться избыточным для членов вашей команды, оно, |
||
| 2335 | тем не менее, может содержать полезную информацию для других. Например, |
||
| 2336 | возвращаемый тип `std::make_unique<foo>()` можно считать очевидным, то для |
||
| 2337 | функции `MyWidgetFactory()`, вероятно, нет. |
||
| 2338 | |||
| 2339 | Эти принципы применимы ко всем случаям вывода типов данных, но детали могут |
||
| 2340 | отличаться для разных случаев. |
||
| 2341 | |||
| 2342 | #### Вывод типов данных в шаблонах функций |
||
| 2343 | |||
| 2344 | Вывод типов данных аргументов шаблонов практически всегда приемлем. Это |
||
| 2345 | стандартный и ожидаемый вариант использования шаблона функции, потому что |
||
| 2346 | шаблоны функций работают похоже на бесконечное множество перегрузок функции. |
||
| 2347 | Как следствие, шаблоны функций почти всегда разрабатываются так, чтобы вывод |
||
| 2348 | типов аргументов шаблона был понятен, безопасен или не компилировался вообще. |
||
| 2349 | |||
| 2350 | #### Вывод типов локальных переменных |
||
| 2351 | |||
| 2352 | Для локальных переменных допустимо использование вывода типов данных для того, |
||
| 2353 | чтобы сделать код понятнее, путем удаления очевидной или нерелевантной |
||
| 2354 | информации о типах данных, что позволяет читателю сосредоточиться на значимых |
||
| 2355 | частях кода, например: |
||
| 2356 | |||
| 2357 | ```c++ |
||
| 2358 | std::unique_ptr<WidgetWithBellsAndWhistles> widget_ptr = |
||
| 2359 | absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); |
||
| 2360 | absl::flat_hash_map<std::string, |
||
| 2361 | std::unique_ptr<WidgetWithBellsAndWhistles>>::const_iterator |
||
| 2362 | it = my_map_.find(key); |
||
| 2363 | std::array<int, 0> numbers = {4, 8, 15, 16, 23, 42}; |
||
| 2364 | ``` |
||
| 2365 | |||
| 2366 | ```c++ |
||
| 2367 | auto widget_ptr = absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); |
||
| 2368 | auto it = my_map_.find(key); |
||
| 2369 | std::array numbers = {4, 8, 15, 16, 23, 42}; |
||
| 2370 | ``` |
||
| 2371 | |||
| 2372 | Бывает, что типы данных содержат смесь полезной информации и нефункционального |
||
| 2373 | кода, например `it` в примере выше: очевидно, что это итератор, и в большинстве |
||
| 2374 | подобных случаев сам контейнер не имеет значения, то тип значения итератора, |
||
| 2375 | вероятно, полезен. В подобных ситуациях часто возможно определить локальные |
||
| 2376 | переменные, чьи явно указанные типы данных смогут донести важную информацию: |
||
| 2377 | |||
| 2378 | ```c++ |
||
| 2379 | auto it = my_map_.find(key); |
||
| 2380 | if(it != my_map_.end()) { |
||
| 2381 | WidgetWithBellsAndWhistles& widget = *it->second; |
||
| 2382 | // Do stuff with `widget` |
||
| 2383 | } |
||
| 2384 | ``` |
||
| 2385 | |||
| 2386 | Если тип - это шаблон класса, в котором параметры неважны, но сам по себе шаблон |
||
| 2387 | - информативен, то можно использовать выведение |
||
| 2388 | (параметров шаблона класса)[#вывод-аргументов-шаблонов-классов]. |
||
| 2389 | |||
| 2390 | Не используйте `decltype(auto)` при наличии более простых альтернатив, т.к. |
||
| 2391 | результат использования не всегда легко предсказуем. |
||
| 2392 | |||
| 2393 | #### Вывод типа возвращаемого значения |
||
| 2394 | |||
| 2395 | Используйте вывод типа возвращаемого значения (и для обычных, и для |
||
| 2396 | лямбда-функций) только в том случае, если тело функции маленькое и содержит |
||
| 2397 | небольшое количество `return`-выражений, потому что в противном случае будет |
||
| 2398 | тяжело понять с первого взгляда, какой тип у возвращаемого значения функции. |
||
| 2399 | Более того, у функции должна быть маленькая область видимости, потому что у |
||
| 2400 | таких функций функции реализация определяет интерфейс, а не наоборот. Публичные |
||
| 2401 | функции в заголовочных файлах никогда не должны использовать вывод типа |
||
| 2402 | возвращаемого значения! |
||
| 2403 | |||
| 2404 | #### Вывод типов параметров |
||
| 2405 | |||
| 2406 | Ключевое слово `auto` в параметрах лямбда-функций стоит использовать с |
||
| 2407 | осторожностью, поскольку в этом случае тип данных определяется кодом, который ее |
||
| 2408 | вызывает, а не определением самой лямбда-функции. Соответственно, явное указание |
||
| 2409 | типа, как правило, более понятно, за исключением тех случаев, когда |
||
| 2410 | лямбда-функция вызывается очень близко к тому месту, где она была объявлена, |
||
| 2411 | или лямбда-функция была передана настолько хорошо известному интерфейсу, что |
||
| 2412 | очевидно, с какими аргументами она будет вызвана (например, `std::sort` в |
||
| 2413 | примере выше). |
||
| 2414 | |||
| 2415 | #### Переменные захвата лямбда-функций |
||
| 2416 | |||
| 2417 | При инициализации переменных захвата предпочтительны |
||
| 2418 | [специальные рекомендации](#лямбда-выражения), которые в целом подменяют общие |
||
| 2419 | правила для использования вывода типов. |
||
| 2420 | |||
| 2421 | #### Структурные привязки |
||
| 2422 | |||
| 2423 | В отличие от других форм вывода типов данных, структурные привязки могут дать |
||
| 2424 | читателю дополнительную информацию, если дать элементам большего объекта |
||
| 2425 | правильные названия. Это значит, что объявление структурной привязки может |
||
| 2426 | улучшить читаемость кода по сравнению с использованием явного типа даже в тех |
||
| 2427 | случаях, когда использование `auto` не рекомендуется. Структурные привязки |
||
| 2428 | особенно хорошо подходят при работе с парами или кортежами |
||
| 2429 | (см. пример с `insert` выше), потому что у них не может быть содержательных |
||
| 2430 | имен, но заметьте, что не стоит использовать |
||
| 2431 | [кортежи и пары](#структуры-против-пар-и-кортежей), за исключением использования |
||
| 2432 | имеющегося API, как в примере с `insert`. |
||
| 2433 | |||
| 2434 | Если объектом привязки является структура, иногда может быть полезно указать |
||
| 2435 | имена, лучше подходящие для данного кода. Однако учитывайте, что они могут быть |
||
| 2436 | менее понятны читателям кода, чем имена полей. |
||
| 2437 | |||
| 2438 | Рекомендуется использовать комментарии для указания имён полей, если они |
||
| 2439 | отличаются от имён привязок. Используйте синтаксис, аналогичный комментариям к |
||
| 2440 | параметрам функций: |
||
| 2441 | |||
| 2442 | ```c++ |
||
| 2443 | auto [/*field_name1=*/ bound_name1, /*field_name2=*/ bound_name2] = ... |
||
| 2444 | ``` |
||
| 2445 | |||
| 2446 | Также, как и с параметрами функций, комментарии могут помочь внешним |
||
| 2447 | инструментам определить ошибки в порядке указания полей. |
||
| 2448 | |||
| 2449 | ### Вывод аргументов шаблонов классов |
||
| 2450 | |||
| 2451 | Используйте вывод аргументов шаблонов класса только для тех шаблонов, которые |
||
| 2452 | явно это поддерживают. |
||
| 2453 | |||
| 2454 | *Вывод аргументов шаблонов класса (Class Template Argument Deduction (CTAD))* |
||
| 2455 | проявляется, когда переменная объявлена с типом шаблона, но без списка |
||
| 2456 | аргументов (даже без угловых скобок): |
||
| 2457 | |||
| 2458 | ```c++ |
||
| 2459 | std::array a = {1, 2, 3}; // Тип `a`: std::array<int, 3> |
||
| 2460 | ``` |
||
| 2461 | |||
| 2462 | Компилятор выводит аргументы из выражения инициализации, используя |
||
| 2463 | *правила вывода шаблона (template deduction guides)*, которые могут быть явными |
||
| 2464 | или неявными. |
||
| 2465 | |||
| 2466 | Явные правила вывода выглядят как объявления функции с возвращаемым значением в |
||
| 2467 | конце, но без ключевого слова `auto`, а название функции такое же, как и |
||
| 2468 | название шаблона. Пример выше опирается на следующие правила вывода: |
||
| 2469 | |||
| 2470 | ```c++ |
||
| 2471 | namespace std { |
||
| 2472 | template <class T, class... U> |
||
| 2473 | array(T, U...) -> std::array<T, 1 + sizeof...(U)>; |
||
| 2474 | } |
||
| 2475 | ``` |
||
| 2476 | |||
| 2477 | Конструкторы в основном шаблоне (на в специализации) также определяют эти |
||
| 2478 | правила, но неявно. |
||
| 2479 | |||
| 2480 | Когда происходит объявление переменной через CTAD, компиляторы выбирает правила |
||
| 2481 | подстановки, используя правила определения перегруженного конструктора, |
||
| 2482 | возвращаемый тип правила становится типом переменной. |
||
| 2483 | |||
| 2484 | Иногда CTAD позволяет уменьшить количество формального кода. |
||
| 2485 | |||
| 2486 | Неявные правила CTAD, выводимые из конструктора могут привести к нежелательному |
||
| 2487 | или совершенно некорректному поведению. Поскольку CTAD был представлен в C++17, |
||
| 2488 | многие библиотеки, написанные до его появления, не учитывали принципы CTAD. |
||
| 2489 | Более того, добавление явного описания правил вывода, чтобы исправить эту |
||
| 2490 | проблему, может сломать код, который использует неявные. |
||
| 2491 | |||
| 2492 | Использование CTAD влечет те же проблемы, что и использование `auto`, потому что |
||
| 2493 | обе эти семантики используют один и тот же механизм. CTAD дает пользователю |
||
| 2494 | больше информации, чем `auto`, но также не дает явного указания, что |
||
| 2495 | необходимая информация была опущена. |
||
| 2496 | |||
| 2497 | Не используйте CTAD в шаблонах классов до тех пор, пока разработчики не добавят |
||
| 2498 | хотя бы одно явное объявление правил вывода шаблона (предполагается, что в |
||
| 2499 | пространстве имён `std` CTAD поддерживается). Желательно, чтобы недопустимое |
||
| 2500 | использование CTAD приводило к предупреждениям компилятора, если он это |
||
| 2501 | поддерживает. |
||
| 2502 | |||
| 2503 | ### Лямбда-выражения |
||
| 2504 | |||
| 2505 | Используйте лямбда-выражения там, где это уместно. В тех случаях, когда |
||
| 2506 | лямбда-функция выходит за пределы текущей области видимости, предпочтительно |
||
| 2507 | использовать явный захват переменных. |
||
| 2508 | |||
| 2509 | Лямбда-выражения - это лаконичный способ создания анонимных объектов-функций. |
||
| 2510 | Они часто полезны при передаче функции в виде аргумента. Например: |
||
| 2511 | |||
| 2512 | ``` c++ |
||
| 2513 | std::sort(v.begin(), v.end(), [](int x, int y) { |
||
| 2514 | return weight(x) < weight(y); |
||
| 2515 | }); |
||
| 2516 | ``` |
||
| 2517 | |||
| 2518 | Лямбда-функции позволяют захватывать переменные из окружающей области видимости |
||
| 2519 | как явно, по имени, так и неявно, используя захват по-умолчанию. Для явного |
||
| 2520 | захвата необходимо перечислить все требуемые переменные в виде значений или |
||
| 2521 | ссылок: |
||
| 2522 | |||
| 2523 | ```c++ |
||
| 2524 | int weight = 3; |
||
| 2525 | int sum = 0; |
||
| 2526 | |||
| 2527 | // Захват `weight` по значению и `sum` по ссылке. |
||
| 2528 | std::for_each(v.begin(), v.end(), [weight, &sum](int x) { |
||
| 2529 | sum += weight * x; |
||
| 2530 | }); |
||
| 2531 | ``` |
||
| 2532 | |||
| 2533 | Неявный захват по-умолчанию применяется ко всем переменным, используемым в теле |
||
| 2534 | лямбда-функции, в том числе к `this`, если используются члены класса: |
||
| 2535 | |||
| 2536 | ```c++ |
||
| 2537 | const std::vector<int> lookup_table = ...; |
||
| 2538 | std::vector<int> indices = ...; |
||
| 2539 | |||
| 2540 | // Захват `lookup_table` по ссылке, сортировка `indices` по значению |
||
| 2541 | // ассоциированных элементов из `lookup_table`. |
||
| 2542 | std::sort(indices.begin(), indices.end(), [&](int a, int b) { |
||
| 2543 | return lookup_table[a] < lookup_table[b]; |
||
| 2544 | }); |
||
| 2545 | ``` |
||
| 2546 | |||
| 2547 | В захвате переменной можно использовать выражения инициализации, что может быть |
||
| 2548 | использовано при захвате перемещаемых переменных по значению или в других |
||
| 2549 | случаях, когда обычные правила захвата не подходят: |
||
| 2550 | |||
| 2551 | ```c++ |
||
| 2552 | std::unique_ptr<Foo> foo = ...; |
||
| 2553 | [foo = std::move(foo)] () { |
||
| 2554 | ... |
||
| 2555 | } |
||
| 2556 | ``` |
||
| 2557 | |||
| 2558 | Для такого захвата, часто называемого захват с инициализацией (init capture) или |
||
| 2559 | генерализированный захват (generalized lambda capture) не нужен захват |
||
| 2560 | чего-либо из окружающей области видимости. Такой синтаксис - это полностью |
||
| 2561 | обобщенный способ определить любые переменные в лямбда-функцию: |
||
| 2562 | |||
| 2563 | ```c++ |
||
| 2564 | [foo = std::vector<int>({1, 2, 3})] () { |
||
| 2565 | ... |
||
| 2566 | } |
||
| 2567 | ``` |
||
| 2568 | |||
| 2569 | Тип такой переменной выводится с полным соответствием с правилами использования |
||
| 2570 | `auto`. |
||
| 2571 | |||
| 2572 | * Лямбда-функции - это очень лаконичный способ определения объектов-функций для |
||
| 2573 | того, чтобы использовать из с алгоритмами из STL, что улучшает читаемость. |
||
| 2574 | * Правильное использование неявного захвата может устранить избыточность и |
||
| 2575 | выявить важные исключения при захвате. |
||
| 2576 | * Лямбда-функции, совместно с `std::function` и `std::bind` могут |
||
| 2577 | использоваться как обобщенный механизм обратного вызова (callback). Они |
||
| 2578 | упрощают разработку кода, который использует функции в качестве аргументов. |
||
| 2579 | |||
| 2580 | --- |
||
| 2581 | |||
| 2582 | * Захват переменных может быть источником ошибок, связанных с висячими |
||
| 2583 | ссылками, если лямбда-функция выходит за пределы текущей области видимости. |
||
| 2584 | * Неявный захвате переменных по значению может вводить в заблуждение, |
||
| 2585 | поскольку приводит к проблеме висячих указателей. Захват указателей по |
||
| 2586 | значению не приводит к глубокому копированию объектов и часто приводит к тем |
||
| 2587 | же проблемам с областью видимости, что и захват переменных по ссылке. Это |
||
| 2588 | особенно запутывает, при получении `this` по значению, поскольку использование |
||
| 2589 | `this` часто неявно. |
||
| 2590 | * Захват переменных на самом деле создает новые переменные (неважно, имеются ли |
||
| 2591 | в захвате выражения инициализации или нет), но они совсем не похожи на любое |
||
| 2592 | объявление переменных в C++. В частности, нигде не указывается тип переменной, |
||
| 2593 | даже ключевое слово `auto` (есть возможность указать тип данных неявно, |
||
| 2594 | например, через приведение типов). Сложно даже понять, что это определения |
||
| 2595 | переменных. |
||
| 2596 | * Использование лямбда-функций может затруднить понимание кода. Сложные, |
||
| 2597 | вложенные лямбда функции могут сделать код очень трудночитаемым. |
||
| 2598 | |||
| 2599 | _Вывод:_ |
||
| 2600 | * Используйте лямбда-функции в подходящих случаях, используйте форматирование, |
||
| 2601 | описанное [ниже](#формат-лямбда-выражений). |
||
| 2602 | * Используйте явный захват переменных в том случае, если лямбда-функция может |
||
| 2603 | выйти за пределы текущей области видимости. Например, вместо: |
||
| 2604 | |||
| 2605 | ``` c++ |
||
| 2606 | { |
||
| 2607 | Foo foo; |
||
| 2608 | ... |
||
| 2609 | executor->Schedule([&] { this->frobnicate(foo); }) |
||
| 2610 | ... |
||
| 2611 | } |
||
| 2612 | // ПЛОХО! При беглом просмотре можно упустить, что лямбда использует |
||
| 2613 | // ссылку на foo и this (если frobnicate является членом класса). |
||
| 2614 | // Если лямбда вызывается после возврата из текущей функции, то |
||
| 2615 | // это приведёт к проблемам, т.к. foo и другие объекты могут быть |
||
| 2616 | // уже разрушены. |
||
| 2617 | ``` |
||
| 2618 | Лучше написать: |
||
| 2619 | |||
| 2620 | ``` c++ |
||
| 2621 | { |
||
| 2622 | Foo foo; |
||
| 2623 | ... |
||
| 2624 | executor->Schedule([&foo] { frobnicate(foo); }) |
||
| 2625 | ... |
||
| 2626 | } |
||
| 2627 | // ЛУЧШЕ - Компилятор выдаст ошибку, если frobnicate является методом класса. |
||
| 2628 | // Также явно указано, что foo захватывается по ссылке. |
||
| 2629 | ``` |
||
| 2630 | * Используйте захват по-умолчанию по ссылке (`[&]`), только если время жизни |
||
| 2631 | лямбда-функции явно короче чем у любой переменной. |
||
| 2632 | * Используйте захват по-умолчанию по значению (`[=]`), только как средство |
||
| 2633 | захвата нескольких переменных для короткой лямбда-функции. |
||
| 2634 | Не рекомендуется писать лямбда-функции с объёмным и сложным кодом вместе с |
||
| 2635 | захватом по-умолчанию по значению. |
||
| 2636 | * Используйте захват только для существующих переменных из текущей области |
||
| 2637 | видимости. Не используйте захват с выражением инициализации |
||
| 2638 | только для того, чтобы дать переменным более понятные имена или чтобы изменить |
||
| 2639 | значения текущих имен. Вместо этого создайте новую переменную традиционным |
||
| 2640 | способом, а потом захватывайте их или сделайте обычную функцию. |
||
| 2641 | * Изучите секцию про |
||
| 2642 | [вывод типов данных](#вывод-типов-данных-в-том-числе-auto). |
||
| 2643 | |||
| 2644 | ### Шаблонное метапрограммирование |
||
| 2645 | |||
| 2646 | Избегайте сложного шаблонного метапрограммирования. |
||
| 2647 | |||
| 2648 | *Шаблонное метапрограммирование (template metaprogramming)* относится к |
||
| 2649 | методикам, которые используют тот факт, что механизм |
||
| 2650 | *конкретизации шаблона (template instantiation)* является полным по Тьюрингу и |
||
| 2651 | может быть использовать для произвольных вычислений во времени компиляции. |
||
| 2652 | |||
| 2653 | Метапрограммирование позволит создавать очень гибкие, высокопроизводительные и |
||
| 2654 | типобезопасные интерфейсы. Каркасы, как GoogleTest, |
||
| 2655 | `std::tuple`, `std::function`, `boost::spirit` будут невозможны без этого. |
||
| 2656 | |||
| 2657 | Методики, использующиеся в шаблонном метапрограммировании часто не до конца |
||
| 2658 | понятны кому-либо, кроме экспертов языка. Код, использующий шаблоны сложным |
||
| 2659 | образом, часто нечитаемый, и его трудно отлаживать и сопровождать. |
||
| 2660 | |||
| 2661 | Сообщения компилятора при использовании шаблонного метапрограммирования часто |
||
| 2662 | скудны и малопонятны: Даже если сам интерфейс простой, сложные детали реализации |
||
| 2663 | становятся видимыми, когда пользователь делает что-то неправильно. |
||
| 2664 | |||
| 2665 | Шаблонное метапрограммирование усложняет рефакторинг, поскольку затрудняет |
||
| 2666 | работу утилит для рефакторинга. Во-первых, шаблонный код раскрывается во |
||
| 2667 | множествах контекстов, и сложно проверить, что рефакторинг будет работать |
||
| 2668 | нормально во всех случаях. |
||
| 2669 | Во-вторых, некоторые утилиты для рефакторинга работают с AST, которое отображает |
||
| 2670 | код уже после конкретизации всех шаблонов. |
||
| 2671 | |||
| 2672 | _Вывод:_ |
||
| 2673 | |||
| 2674 | Шаблонное метапрограммирование часто позволяет разрабатывать чистые и простые в |
||
| 2675 | использовании интерфейсы, невозможные без использования данной технологии, но |
||
| 2676 | часто есть соблазн сделать код заумным. Лучше использовать шаблоны в небольшом |
||
| 2677 | количестве низкоуровневых компонентов, сложность поддержки которых бы |
||
| 2678 | компенсировалась простотой их использования. |
||
| 2679 | |||
| 2680 | Подумайте дважды, прежде чем использовать метапрограммирования или другие |
||
| 2681 | сложные техники, основанные на шаблонах: Может ли средний программист в команде |
||
| 2682 | понять этот код настолько хорошо, чтобы чтобы сопровождать его после того, как |
||
| 2683 | вы займетесь другими проектами. Сможет ли не C++-программист, читающий код, |
||
| 2684 | понять, что он делает и сообщения об ошибках, которые возникнут при неправильном |
||
| 2685 | использовании. Если используются рекурсивную конкретизацию шаблонов, списки |
||
| 2686 | типов, метафункции, шаблоны выражений или используется SFINAE или трюк с |
||
| 2687 | `sizeof` для разрешения перегрузки функции — скорее всего вы зашли слишком |
||
| 2688 | далеко. |
||
| 2689 | |||
| 2690 | Прилагайте усилия к минимизации и изоляции сложности, когда используете |
||
| 2691 | шаблонное метапрограммирование. По возможности, скрывайте от пользователя код, |
||
| 2692 | использующий метапрограммирование. Тщательно документируйте, как использовать |
||
| 2693 | код, на что будет похож "сгенерированный" код. Обратите внимание на сообщения об |
||
| 2694 | ошибках, которые выдает компилятор, когда пользователи будут совершать |
||
| 2695 | ошибки. Сообщений об ошибках - часть вашего интерфейса и ваш код должен быть |
||
| 2696 | настроен для максимального удобства пользователя. |
||
| 2697 | |||
| 2698 | ### Boost |
||
| 2699 | |||
| 2700 | Используйте только одобренные библиотеки из коллекции Boost. |
||
| 2701 | |||
| 2702 | *Boost* это популярная коллекция проверенных, бесплатных и открытых |
||
| 2703 | библиотек C++. |
||
| 2704 | |||
| 2705 | В целом код Boost является высококачественным, портируемым и во многом |
||
| 2706 | дополняет стандартную библиотеку C++, например, в таких областях как свойства |
||
| 2707 | типов (*type_traits*) или улучшенные связыватели (*binder*). |
||
| 2708 | |||
| 2709 | Некоторые библиотеки Boost поощряют создание кода, который ухудшает |
||
| 2710 | читаемость: используется метапрограммирование или другие продвинутые техники |
||
| 2711 | на шаблонах, а также чрезмерно функциональный стиль. |
||
| 2712 | |||
| 2713 | _Вывод_ |
||
| 2714 | |||
| 2715 | Чтобы читаемость кода оставалась высокой для всех, кто осуществляет его |
||
| 2716 | поддержку, разрешены к использованию только некоторые библиотеки из коллекции |
||
| 2717 | Boost. В настоящее время это: |
||
| 2718 | * `boost/call_traits.hpp` |
||
| 2719 | * `boost/compressed_pair.hpp` |
||
| 2720 | * Boost Graph Library (BGL) из `boost/graph`, за исключением сериализации |
||
| 2721 | (`adj_list_serialize.hpp`) и параллельных/распределённых алгоритмов и структур |
||
| 2722 | данных (`boost/graph/parallel/*` и `boost/graph/distributed/*`). |
||
| 2723 | * Property Map из `boost/property_map`, за исключением |
||
| 2724 | параллельных/распределённых (`boost/property_map/parallel/*`). |
||
| 2725 | * `boost/iterator` |
||
| 2726 | * Часть Polygon, которая работает с построением диаграмм Вороного и не |
||
| 2727 | зависит от остальной части Polygon: `boost/polygon/voronoi_builder.hpp`, |
||
| 2728 | `boost/polygon/voronoi_diagram.hpp`, и |
||
| 2729 | `boost/polygon/voronoi_geometry_type.hpp` |
||
| 2730 | * `boost/bimap` |
||
| 2731 | * `boost/math/distributions` |
||
| 2732 | * `boost/math/special_functions` |
||
| 2733 | * Функции нахождения корня из `boost/math/tools` |
||
| 2734 | * `boost/multi_index` |
||
| 2735 | * `boost/heap` |
||
| 2736 | * flat-контейнеры библиотеки Container: `boost/container/flat_map` и |
||
| 2737 | `boost/container/flat_set` |
||
| 2738 | * `boost/intrusive` |
||
| 2739 | * `boost/sort` |
||
| 2740 | * `boost/preprocessor` |
||
| 2741 | |||
| 2742 | В настоящее время прорабатывается вопрос о добавлении других библиотек Boost в |
||
| 2743 | этот список, так что он может в будущем дополняться. Скорее всего, дождемся |
||
| 2744 | новой версии стиля то Google. |
||
| 2745 | |||
| 2746 | ### Остальные возможности C++ |
||
| 2747 | |||
| 2748 | Некоторые расширения современного C++, как и Boost, провоцирует писать плохо |
||
| 2749 | читаемый код. Другие расширения дублируют функционал, который доступен через |
||
| 2750 | существующие механизмы, что может привести к путанице и дополнительной |
||
| 2751 | конвертации кода. |
||
| 2752 | |||
| 2753 | Настоятельно не рекомендуется использовать следующие возможности C++: |
||
| 2754 | * Рациональные числа времени компиляции (`<ratio>`), т.к. за интерфейсом может |
||
| 2755 | стоять сложный шаблон. |
||
| 2756 | * Заголовочные файлы `<cfenv>` и `<fenv.h>`, поскольку многие компиляторы не |
||
| 2757 | поддерживают корректную работу этого функционала. |
||
| 2758 | * Заголовочный файл `<filesystem>`, который недостаточно протестирован, и |
||
| 2759 | подвержен уязвимостям в безопасности. |
||
| 2760 | |||
| 2761 | ### Псевдонимы (Aliases) |
||
| 2762 | |||
| 2763 | Открытые псевдонимы можно использовать для упрощения работы пользователя API, |
||
| 2764 | они должны быть хорошо задокументировали. |
||
| 2765 | |||
| 2766 | Есть несколько способов для создания имен, которые будут псевдонимами для |
||
| 2767 | других сущностей: |
||
| 2768 | |||
| 2769 | ```c++ |
||
| 2770 | typedef Foo Bar; |
||
| 2771 | using Bar = Foo; |
||
| 2772 | using other_namespace::Foo; |
||
| 2773 | ``` |
||
| 2774 | |||
| 2775 | В новои коде использование ключевого слова `using` предпочтительнее, чем |
||
| 2776 | `typedef`, потому что оно предоставляет более согласованный с остальным C++ |
||
| 2777 | синтаксис и работает с шаблонами. |
||
| 2778 | |||
| 2779 | Как и другие объявления, псевдонимы, объявленные в заголовочном файле - это |
||
| 2780 | часть открытого API, до тех пор, пока они не в определении функции, не в |
||
| 2781 | закрытой секции класса, или не находятся во внутреннем пространстве имен. |
||
| 2782 | Псевдонимы в других частях кода, как `.cpp`-файлы - это детали реализации и не |
||
| 2783 | ограничиваются данными правилами. |
||
| 2784 | |||
| 2785 | * Псевдонимы могут улучшить читаемость, упрощая сложные или слишком длинные |
||
| 2786 | имена. |
||
| 2787 | * Псевдонимы могут уменьшить дублирование, объявляя в одном месте тип, который |
||
| 2788 | будет использоваться повторно, что *может* облегчить читаемость. |
||
| 2789 | |||
| 2790 | --- |
||
| 2791 | |||
| 2792 | * Публичные псевдонимы увеличивают количество сущностей в API, что увеличивает |
||
| 2793 | сложность. |
||
| 2794 | * Клиентский код легко может начать полагаться на особенности открытых |
||
| 2795 | псевдонимов, что усложняет внесение изменений. |
||
| 2796 | * Есть соблазн поместить в публичный API псевдоним, который должен |
||
| 2797 | использоваться только во внутренней реализации, что усложнит сопровождение. |
||
| 2798 | * Псевдонимы увеличивают риск возникновения коллизий имен. |
||
| 2799 | * Псевдонимы могут ухудшить читаемость, давая знакомым конструкциям незнакомые |
||
| 2800 | имена. |
||
| 2801 | * Псевдонимы типов создают неясный контракт в API: неясно, всегда ли псевдоним |
||
| 2802 | будет соответствовать указанному псевдониму, иметь такой же API, или его можно |
||
| 2803 | использовать только в определенных местах. |
||
| 2804 | |||
| 2805 | _Вывод:_ |
||
| 2806 | Не вводите псевдонимы в открытый API только для облегчения кодирования в |
||
| 2807 | реализации. Задача публичного псевдонима - облегчить написание клиентского кода. |
||
| 2808 | |||
| 2809 | При определении открытого псевдонима, документируйте, зачем нужно новое имя, |
||
| 2810 | гарантируется ли, что под псевдонимом всегда будет этот же тип, или есть |
||
| 2811 | какие-то ограничения для его использования. |
||
| 2812 | Это укажет пользователю, может ли он считать типы взаимозаменяемыми или |
||
| 2813 | следует следовать более специфическим правилам, что позволит реализации иметь |
||
| 2814 | некоторую степень свободы. |
||
| 2815 | |||
| 2816 | Не объявляйте псевдонимы пространств имен в своем открытом API |
||
| 2817 | (см. [Пространства имен](#пространства-имен). |
||
| 2818 | |||
| 2819 | Например, использование следующих псевдонимов задокументировано: |
||
| 2820 | |||
| 2821 | ``` c++ |
||
| 2822 | namespace mynamespace { |
||
| 2823 | |||
| 2824 | // Используется для хранения измерений. DataPoint может меняться с Bar* на |
||
| 2825 | // другой внутренний тип, его следует трактовать как абстрактный указатель. |
||
| 2826 | using DataPoint = foo::Bar*; |
||
| 2827 | |||
| 2828 | // Набор измерений. Добавлен для удобства пользователя. |
||
| 2829 | using TimeSeries = |
||
| 2830 | std::unordered_set<DataPoint, std::hash<DataPoint>, DataPointComparator>; |
||
| 2831 | } // namespace mynamespace |
||
| 2832 | ``` |
||
| 2833 | |||
| 2834 | А приведенных ниже - нет: |
||
| 2835 | |||
| 2836 | ```c++ |
||
| 2837 | namespace mynamespace { |
||
| 2838 | |||
| 2839 | // Плохо: непонятно, как это использовать. |
||
| 2840 | using DataPoint = foo::Bar*; |
||
| 2841 | using std::unordered_set; // Плохо: это для внутреннего удобства |
||
| 2842 | using std::hash; // Плохо: это для внутреннего удобства |
||
| 2843 | typedef unordered_set<DataPoint, hash<DataPoint>, DataPointComparator> |
||
| 2844 | TimeSeries; |
||
| 2845 | } // namespace mynamespace |
||
| 2846 | ``` |
||
| 2847 | |||
| 2848 | Локальные псевдонимы, созданные для удобства внутри `.cpp`-файлов, закрытых |
||
| 2849 | секций классов, внутренних пространств имен - это совершенно нормально: |
||
| 2850 | |||
| 2851 | ```c++ |
||
| 2852 | // В .cpp файле |
||
| 2853 | using foo::Bar; |
||
| 2854 | ``` |
||
| 2855 | |||
| 2856 | ## Соглашения об именовании |
||
| 2857 | |||
| 2858 | Наиболее важные правила стиля кодирования приходятся на именование. Вид имени |
||
| 2859 | сразу же (без поиска объявления) говорит нам что это: тип, переменная, функция, |
||
| 2860 | константа, макрос и т.д. Правила именования могут быть произвольными, |
||
| 2861 | однако куда более важна их согласованность чем индивидуальные предпочтения. |
||
| 2862 | Так что независимо от того, находите вы правила разумными или нет, |
||
| 2863 | их необходимо соблюдать. |
||
| 2864 | |||
| 2865 | ### Основные правила именования |
||
| 2866 | |||
| 2867 | Следует использовать имена, которые будут понятны даже людям, |
||
| 2868 | ~~обременённым лишь тремя классами образования~~ работающим в разных командах. |
||
| 2869 | |||
| 2870 | Имя должно говорить о назначении и применимости объекта. Не стоит заморачиваться |
||
| 2871 | по поводу длины строк при выборе имени, поскольку более длинное и понятное имя |
||
| 2872 | всегда лучше, чем короткое и непонятное. Стоит минимизировать использование |
||
| 2873 | аббревиатур, значение которых может быть неизвестно человеку, не работающему над |
||
| 2874 | проектом (в частности акронимы и инициализмы). Запрещается порождать |
||
| 2875 | аббревиатуры путём исключения букв из слов. Допускается использование |
||
| 2876 | общеизвестных аббревиатур (из глоссария, например). В целом, длина имени должна |
||
| 2877 | соответствовать области видимости. Например, n - подходящее имя внутри функции в |
||
| 2878 | 5 строк, но как имя члена класса - коротковато. |
||
| 2879 | |||
| 2880 | _Хороший код:_ |
||
| 2881 | ``` cpp |
||
| 2882 | class MyClass |
||
| 2883 | { |
||
| 2884 | public: |
||
| 2885 | int countFooErrors(const std::vector<Foo>& foos) { |
||
| 2886 | int n = 0; // Чёткий смысл для небольшой области видимости |
||
| 2887 | for(const auto& foo : foos) { |
||
| 2888 | ... |
||
| 2889 | ++n; |
||
| 2890 | } |
||
| 2891 | return n; |
||
| 2892 | } |
||
| 2893 | void doSomethingImportant() { |
||
| 2894 | std::string fqdn = ...; // Известная аббревиатура полного доменного |
||
| 2895 | // имени ( Fully Qualified Domain Name) |
||
| 2896 | } |
||
| 2897 | private: |
||
| 2898 | const int kMaxAllowedConnections = ...; // Чёткий смысл для контекста |
||
| 2899 | }; |
||
| 2900 | ``` |
||
| 2901 | |||
| 2902 | _Плохой код:_ |
||
| 2903 | ``` cpp |
||
| 2904 | class MyClass |
||
| 2905 | { |
||
| 2906 | public: |
||
| 2907 | int countFooErrors(const std::vector<Foo>& foos) { |
||
| 2908 | int total_number_of_foo_errors = 0; // Слишком подробное имя в контексте короткой функции |
||
| 2909 | for(int foo_index = 0; foo_index < foos.size(); ++foo_index) { // Лучше использовать `i` |
||
| 2910 | ... |
||
| 2911 | ++total_number_of_foo_errors; |
||
| 2912 | } |
||
| 2913 | return total_number_of_foo_errors; |
||
| 2914 | } |
||
| 2915 | void doSomethingImportant() { |
||
| 2916 | int cstmr_id = ...; // Сокращённое слово (удалены буквы) |
||
| 2917 | } |
||
| 2918 | private: |
||
| 2919 | const int kNum = ...; // В контексте класса очень нечёткое имя |
||
| 2920 | }; |
||
| 2921 | ``` |
||
| 2922 | |||
| 2923 | Отметим, что общепринятые сокращения и аббревиатуры также допустимы: `i` для |
||
| 2924 | итератора или счётчика, `T` для параметра шаблона. |
||
| 2925 | |||
| 2926 | В дальнейшем будем считать, что "слово" - это всё, что пишется на английском |
||
| 2927 | без пробелов, в том числе и аббревиатуры. Для имён, написанных в смешанном стиле |
||
| 2928 | ("camel case" или "Pascal case"), в которых первая буква каждого слова является |
||
| 2929 | заглавной, следует относиться к аббревиатурам как к целому слову. Например, |
||
| 2930 | предпочтительно использовать `StartRpc()` вместо `StartRPC()`. |
||
| 2931 | |||
| 2932 | Параметры шаблонов следуют правилом именования своих категорий: |
||
| 2933 | [имена типов](#имена-типов) для типов, [имена переменных](#имена-переменных) |
||
| 2934 | для переменных. |
||
| 2935 | |||
| 2936 | ### Имена файлов |
||
| 2937 | |||
| 2938 | Имена файлов должны содержать только строчные буквы. В качестве разделителей |
||
| 2939 | следует использовать подчёркивание (_) или дефис (-). Это зависит от выбранного |
||
| 2940 | разделителя в проекте. Если нет единого подхода - используйте подчёркивание. |
||
| 2941 | |||
| 2942 | _Примеры подходящих имён:_ |
||
| 2943 | * `my_useful_class.cpp` |
||
| 2944 | * `my-useful-class.cpp` |
||
| 2945 | * `myusefulclass.cpp` |
||
| 2946 | * `myusefulclass_test.cpp` - *_unittest и _regtest больше не используются*. |
||
| 2947 | |||
| 2948 | Файлы C++ должны иметь формат файла `.cpp`, а заголовочные - `.hpp`. |
||
| 2949 | Дополнительные файлы, подключаемые как текст, должны именоваться как .inc |
||
| 2950 | (см. секцию |
||
| 2951 | [Самодостаточные заголовочные файлы](#самодостаточные-заголовочные-файлы)). |
||
| 2952 | |||
| 2953 | Не используйте имена, которые уже представлены в `/usr/include`, |
||
| 2954 | такие как `db.h`. |
||
| 2955 | |||
| 2956 | Старайтесь давать файлам специфичные имена. Например, `http_server_logs.hpp` |
||
| 2957 | лучше чем `logs.hpp`. Когда файлы используются парами, лучше давать им |
||
| 2958 | одинаковые имена. Например, `foo_bar.hpp` и `foo_bar.cpp` (и содержат класс |
||
| 2959 | `FooBar`). |
||
| 2960 | |||
| 2961 | ### Имена типов |
||
| 2962 | |||
| 2963 | Имена типов начинаются с заглавной буквы, каждое новое слово также начинается с |
||
| 2964 | заглавной. Подчёркивания не используются: `MyExcitingClass`, `MyExcitingEnum`. |
||
| 2965 | |||
| 2966 | Имена всех типов - классов, структур, псевдонимов, перечислений, параметров |
||
| 2967 | шаблонов - именуются в одинаковом стиле. |
||
| 2968 | |||
| 2969 | ``` cpp |
||
| 2970 | // классы и структуры |
||
| 2971 | class UrlTable { ... |
||
| 2972 | class UrlTableTester { ... |
||
| 2973 | struct UrlTableProperties { ... |
||
| 2974 | |||
| 2975 | // typedefs |
||
| 2976 | typedef hash_map<UrlTableProperties *, std::string> PropertiesMap; |
||
| 2977 | |||
| 2978 | // использование псевдонимов |
||
| 2979 | using PropertiesMap = hash_map<UrlTableProperties *, std::string>; |
||
| 2980 | |||
| 2981 | // перечисления |
||
| 2982 | enum UrlTableErrors { ... |
||
| 2983 | ``` |
||
| 2984 | |||
| 2985 | ### Имена переменных |
||
| 2986 | |||
| 2987 | Имена переменных (включая параметры функций) и членов данных пишутся строчными |
||
| 2988 | буквами с подчёркиванием между словами. Члены данных классов (не структур) |
||
| 2989 | дополняются префиксом `m_` . Например: `a_local_variable` , |
||
| 2990 | `a_struct_data_member` , `m_a_class_data_member`. |
||
| 2991 | |||
| 2992 | #### Имена обычных переменных |
||
| 2993 | |||
| 2994 | Например: |
||
| 2995 | ``` cpp |
||
| 2996 | std::string table_name; // OK - строчные буквы с подчёркиванием |
||
| 2997 | ``` |
||
| 2998 | ``` cpp |
||
| 2999 | std::string tableName; // Плохо - смешанный стиль |
||
| 3000 | ``` |
||
| 3001 | |||
| 3002 | #### Члены данных класса |
||
| 3003 | |||
| 3004 | Члены данных классов, статические и нестатические, именуются как обычные |
||
| 3005 | переменные с добавлением префикса `m_`. |
||
| 3006 | |||
| 3007 | ``` cpp |
||
| 3008 | class TableInfo { |
||
| 3009 | ... |
||
| 3010 | private: |
||
| 3011 | std::string m_table_name_; // OK - префикс в начале |
||
| 3012 | static Pool<TableInfo>* m_pool; // OK. |
||
| 3013 | }; |
||
| 3014 | ``` |
||
| 3015 | |||
| 3016 | #### Члены данных структуры |
||
| 3017 | |||
| 3018 | Члены данных структуры, статические и нестатические, именуются как обычные |
||
| 3019 | переменные. К ним не добавляется префикс. |
||
| 3020 | |||
| 3021 | ``` cpp |
||
| 3022 | struct UrlTableProperties { |
||
| 3023 | std::string name; |
||
| 3024 | int num_entries; |
||
| 3025 | static Pool<UrlTableProperties>* pool; |
||
| 3026 | }; |
||
| 3027 | ``` |
||
| 3028 | См. также [Структуры против классов](#структуры-против-классов), где описано |
||
| 3029 | когда использовать структуры, а когда - классы. |
||
| 3030 | |||
| 3031 | ### Имена констант |
||
| 3032 | |||
| 3033 | Переменные, объявленные с использованием `constexpr` или `const`, не меняются в |
||
| 3034 | ходе выполнения программы. Их имена начинаются с символа "k", далее идёт имя в |
||
| 3035 | смешанном стиле (прописные и строчные буквы). Подчёркивание может быть |
||
| 3036 | использовано в редких случаях когда прописные буквы не могут использоваться для |
||
| 3037 | разделения. Например: |
||
| 3038 | ``` cpp |
||
| 3039 | const int kDaysInAWeek = 7; |
||
| 3040 | const int kAndroid8_0_0 = 24; // Android 8.0.0 |
||
| 3041 | ``` |
||
| 3042 | Все аналогичные константные объекты со статическим типом хранилища (т.е. |
||
| 3043 | статические или глобальные, подробнее [тут](#область-видимости))именуются |
||
| 3044 | так же. Это соглашение является необязательным для переменных в других типах |
||
| 3045 | хранилища (например, автоматические константные объекты). |
||
| 3046 | |||
| 3047 | ### Имена функций |
||
| 3048 | |||
| 3049 | Обычные функции именуются в *смешанном стиле(camel case)* (прописные и строчные |
||
| 3050 | буквы); функции доступа к переменным (*accessor* и *mutator*) должны иметь |
||
| 3051 | стиль, аналогичный целевой переменной. |
||
| 3052 | Обычно имя функции начинается со строчной буквы, а каждое новое слово в имени |
||
| 3053 | пишется с прописной буквы. |
||
| 3054 | ``` cpp |
||
| 3055 | addTableEntry() |
||
| 3056 | deleteUrl() |
||
| 3057 | openFileOrDie() |
||
| 3058 | ``` |
||
| 3059 | Аналогичные правила применяются для констант в области класса или пространства |
||
| 3060 | имён (namespace) которые представляют собой часть API и должны выглядеть как |
||
| 3061 | функции (и то, что они не функции - некритично) |
||
| 3062 | |||
| 3063 | Accessor-ы и mutator-ы (функции get и set) могут именоваться наподобие |
||
| 3064 | соответствующих переменных. Они часто соответствуют реальным переменным-членам, |
||
| 3065 | однако это не обязательно. Например, int count() и void set_count(int count). |
||
| 3066 | |||
| 3067 | ### Именование пространства имён (namespace) |
||
| 3068 | |||
| 3069 | Пространство имён называется только строчными буквами, отдельные слова |
||
| 3070 | разделяются подчёркиванием `(_)`. Пространство имён верхнего уровня основывается |
||
| 3071 | на названии проекта. Избегайте коллизий вложенных имён и хорошо известных имён |
||
| 3072 | пространств верхнего уровня. |
||
| 3073 | Пространство имён верхнего уровня - это обычно название проекта или команды |
||
| 3074 | (которая делала код). Код обычно располагается в директории (или поддиректории) |
||
| 3075 | с именем, соответствующим пространству имён. |
||
| 3076 | Правило [не использовать аббревиатуры](#основные_правила_именования) применимы и |
||
| 3077 | к пространствам имён. Коду внутри вряд ли потребуется упоминание пространства |
||
| 3078 | имён, поэтому аббревиатуры - это лишнее. |
||
| 3079 | Избегайте использования известных названий для вложенных пространств имён. |
||
| 3080 | Коллизии между именами могут привести к сюрпризам при сборке. В частности, не |
||
| 3081 | создавайте вложенных пространств имён с именем `std`. Рекомендуются уникальные |
||
| 3082 | идентификаторы проекта (`websearch::index`, `websearch::index_util`) вместо |
||
| 3083 | небезопасных к коллизиям `websearch::util`. |
||
| 3084 | Для внутренних пространств имён коллизии могут возникать при добавлении другого |
||
| 3085 | кода (внутренние вспомогательные функции имеют свойство повторяться у разных |
||
| 3086 | команд). В этом случае хорошо помогает использование имени файла для именования |
||
| 3087 | пространства имён, например `websearch::index::frobber_internal` для |
||
| 3088 | использования в `frobber.hpp`. |
||
| 3089 | |||
| 3090 | ### Имена перечислений |
||
| 3091 | |||
| 3092 | Перечисления, как с ограничениями на область видимости , так и без, должны |
||
| 3093 | именоваться либо как [константы](#имена-констант), либо как [макросы] |
||
| 3094 | (#имена-макросов). Т.е.: либо `kEnumName`, либо `ENUM_NAME`. |
||
| 3095 | |||
| 3096 | Предпочтительно именовать отдельные значения в перечислении как константы, |
||
| 3097 | однако, допустимо именовать как макросы. Имя самого перечисления: |
||
| 3098 | `UrlTableErrors` и `AlternateUrlTableErrors` - это тип. Следовательно, |
||
| 3099 | используется смешанный стиль. |
||
| 3100 | |||
| 3101 | ``` cpp |
||
| 3102 | enum UrlTableErrors { |
||
| 3103 | kOk = 0, |
||
| 3104 | kErrorOutOfMemory, |
||
| 3105 | kErrorMalformedInput, |
||
| 3106 | }; |
||
| 3107 | enum AlternateUrlTableErrors { |
||
| 3108 | OK = 0, |
||
| 3109 | OUT_OF_MEMORY = 1, |
||
| 3110 | MALFORMED_INPUT = 2, |
||
| 3111 | }; |
||
| 3112 | ``` |
||
| 3113 | |||
| 3114 | Вплоть до января 2009 года стиль именования значений перечисления был как у |
||
| 3115 | макросов. Это создавало проблемы дублирования имён макросов и значений |
||
| 3116 | перечислений. Применение стиля констант решает проблему и в новом коде |
||
| 3117 | предпочтительно использовать стиль констант. Однако, старый код нет |
||
| 3118 | необходимости переписывать, пока нет проблем дублирования. |
||
| 3119 | |||
| 3120 | ### Имена макросов |
||
| 3121 | |||
| 3122 | Вы ведь не собираетесь [определять макросы](#макросы-препроцессора)? |
||
| 3123 | На всякий случай (если собираетесь), они должны выглядеть так: |
||
| 3124 | `MY_MACRO_THAT_SCARES_SMALL_CHILDREN_AND_ADULTS_ALIKE`. |
||
| 3125 | |||
| 3126 | Пожалуйста прочтите как определять макросы. Обычно макросы не должны |
||
| 3127 | использоваться. Однако если они вам абсолютно необходимы, именуйте их |
||
| 3128 | прописными буквами с символами подчёркивания. |
||
| 3129 | |||
| 3130 | ``` cpp |
||
| 3131 | #define ROUND(x) ... |
||
| 3132 | #define PI_ROUNDED 3.0 |
||
| 3133 | ``` |
||
| 3134 | |||
| 3135 | ### Исключения из правил именования |
||
| 3136 | |||
| 3137 | Если вам нужно именовать что-то, имеющее аналоги в существующем C или C++ коде, |
||
| 3138 | то следуйте используемому в коде стилю. |
||
| 3139 | |||
| 3140 | ``` cpp |
||
| 3141 | bigopen() |
||
| 3142 | // имя функции, образованное от open() |
||
| 3143 | uint |
||
| 3144 | // похож на стандартный тип |
||
| 3145 | bigpos |
||
| 3146 | // struct или class , образованный от pos |
||
| 3147 | sparse_hash_map |
||
| 3148 | // STL-подобная сущность; следуйте стилю STL |
||
| 3149 | LONGLONG_MAX |
||
| 3150 | // константа, такая же как INT_MAX |
||
| 3151 | ``` |
||
| 3152 | |||
| 3153 | ## Комментарии |
||
| 3154 | |||
| 3155 | Комментарии являются обязательными для кода (если вы планируете его читать). |
||
| 3156 | Следующие правила описывают, что вы должны комментировать и как. |
||
| 3157 | Но помните: хотя комментарии очень важны, идеальный код сам себя документирует. |
||
| 3158 | Использование *говорящих* имён для типов и переменных намного лучше, чем |
||
| 3159 | непонятные имена, которые потом требуется расписывать в комментариях. |
||
| 3160 | |||
| 3161 | Комментируйте код с учётом его следующих читателей: программистов, которым |
||
| 3162 | потребуется разбираться в вашем коде. Учтите, что следующим читателем можете |
||
| 3163 | стать вы сами! |
||
| 3164 | |||
| 3165 | ### Стиль комментариев |
||
| 3166 | |||
| 3167 | Используйте либо `//` либо `/* */`, пока не нарушается единообразие. |
||
| 3168 | |||
| 3169 | Вы можете использовать либо `//` либо `/* */`, однако `//` - *намного* |
||
| 3170 | предпочтительнее. Однако, всегда согласовывайте ваш стиль комментариев с уже |
||
| 3171 | существующим кодом. |
||
| 3172 | |||
| 3173 | ### Комментарии в шапке файла |
||
| 3174 | |||
| 3175 | Данный шаг не является обязательным, однако, стоит упомянуть. В шапку можно |
||
| 3176 | поместить информацию о лицензии, авторах и содержимом файлов. К примеру, |
||
| 3177 | в одном заголовочном файле описано несколько абстракций, будет неплохо описать, |
||
| 3178 | как они связаны друг с другом. |
||
| 3179 | |||
| 3180 | ### Комментарии класса |
||
| 3181 | |||
| 3182 | Объявление класса должно сопровождаться комментарием, оформленным по правилам |
||
| 3183 | [Doxygen]({TODO:ВСТАВИТЬ_ССЫЛКУ}), и располагаться в заголовочном `.hpp`-файле. |
||
| 3184 | В случае, если некие тонкости реализации класса заслуживают отдельного |
||
| 3185 | разъяснения, комментарий с разъяснением следует расположить в `.cpp` файле. |
||
| 3186 | |||
| 3187 | Комментарий к классу должен быть достаточным для понимания: как и когда |
||
| 3188 | использовать класс, дополнительные требования для правильного использования |
||
| 3189 | класса. Описывайте, если требуется, ограничения (предположения) на синхронизацию |
||
| 3190 | в классе. Если экземпляр класса может использоваться из разных потоков, |
||
| 3191 | обязательно распишите правила многопоточного использования. |
||
| 3192 | |||
| 3193 | В комментарии к классу также можно привести короткие примеры кода, показывающие |
||
| 3194 | как проще использовать класс. |
||
| 3195 | |||
| 3196 | Дублировать комментарии в обоих файлах **не нужно**. |
||
| 3197 | |||
| 3198 | ### Комментарии функции |
||
| 3199 | |||
| 3200 | Комментарии к объявлению функции должны описывать использование функции |
||
| 3201 | (кроме самых очевидных случаев). Комментарии к определению функции описывают |
||
| 3202 | реализацию. |
||
| 3203 | |||
| 3204 | #### Объявление функции |
||
| 3205 | |||
| 3206 | Объявление каждой функции должно иметь комментарий прямо перед объявлением: что |
||
| 3207 | функция делает и как ей пользоваться. Комментарий можно опустить, только если |
||
| 3208 | функция простая и использование очевидно, например, функции получения значений |
||
| 3209 | переменных. Старайтесь начинать комментарии в изъявительном наклонении |
||
| 3210 | ("Открывает файл"). Использование повелительного наклонение |
||
| 3211 | ("Открыть файл") - не рекомендуется. Комментарий описывает суть функции, |
||
| 3212 | а не то, как она это делает. |
||
| 3213 | |||
| 3214 | В комментарии к объявлению функции обратите внимание на следующее: |
||
| 3215 | |||
| 3216 | * Что подаётся на вход функции, что возвращается в результате. |
||
| 3217 | * Для функции-члена класса: сохраняет ли экземпляр ссылки на аргументы, нужно ли |
||
| 3218 | освобождать память. |
||
| 3219 | * Выделяет ли функция память, которую должен удалить вызывающий код. |
||
| 3220 | * Могут ли быть аргументы `nullptr`. |
||
| 3221 | * Алгоритмическая сложность функции. |
||
| 3222 | * Допустим ли одновременный вызов из разных потоков. Что с синхронизацией? |
||
| 3223 | |||
| 3224 | Однако не стоит разжёвывать очевидные вещи. |
||
| 3225 | |||
| 3226 | Когда документируйте перегружаемые функции, делайте основной упор на изменениях |
||
| 3227 | по сравнению с исходной функцией. А если изменений нет (что бывает часто), то |
||
| 3228 | дополнительные комментарии вообще не нужны. |
||
| 3229 | |||
| 3230 | Комментируя конструкторы и деструкторы, учитывайте, что читатель кода знает их |
||
| 3231 | назначение. Поэтому комментарий типа "разрушает этот объект" - бестолковый. |
||
| 3232 | Можете описывать, что конструктор делает с аргументами (например, изменение |
||
| 3233 | владения на указатели) или какие именно операции по очистке делает деструктор. |
||
| 3234 | Если всё и так понятно - ничего не комментируйте. Вообще, обычно деструкторы не |
||
| 3235 | имеют комментариев (при объявлении). |
||
| 3236 | |||
| 3237 | Комментарий объявления функции также должен быть оформлен по правилам |
||
| 3238 | [Doxygen]({TODO: Вставить ссылку на Doxygen}). |
||
| 3239 | |||
| 3240 | #### Определение функций |
||
| 3241 | |||
| 3242 | Если есть какие-то хитрости в реализации функции, то можно к определению |
||
| 3243 | добавить объяснительный комментарий. В нём можно описать трюки с кодом, дать |
||
| 3244 | обзор всех этапов вычислений, объяснить выбор той или иной реализации (особенно |
||
| 3245 | если есть лучшие альтернативы). Можете описать принципы синхронизации кусков |
||
| 3246 | кода (здесь блокируем, а здесь рыбу заворачиваем). |
||
| 3247 | |||
| 3248 | Отметим что вы не должны повторять комментарий из объявления функции из `.hpp` |
||
| 3249 | файла и т.п. Можно кратко описать, что функция делает, однако основной упор |
||
| 3250 | должен быть как она это делает. |
||
| 3251 | |||
| 3252 | ### Комментарии к переменным |
||
| 3253 | |||
| 3254 | По хорошему, имя переменной должно сразу говорить что это и зачем, однако в |
||
| 3255 | некоторых случаях требуются дополнительные комментарии. |
||
| 3256 | |||
| 3257 | #### Член данных класса |
||
| 3258 | |||
| 3259 | Назначение каждого члена класса должно быть очевидно. Если есть неочевидные |
||
| 3260 | тонкости (специальные значения, завязки с другими членами, ограничения по |
||
| 3261 | времени жизни) - всё это нужно комментировать. Однако, если типа и имени |
||
| 3262 | достаточно - комментарии добавлять не нужно. |
||
| 3263 | |||
| 3264 | С другой стороны, полезными будут описания особых (и неочевидных) значений |
||
| 3265 | (`nullptr` или -1). Например: |
||
| 3266 | ``` cpp |
||
| 3267 | private: |
||
| 3268 | // Используется для проверки выхода за границы |
||
| 3269 | // -1 - показывает, что мы не знаем сколько записей в таблице |
||
| 3270 | int m_num_total_entries; |
||
| 3271 | ``` |
||
| 3272 | |||
| 3273 | #### Глобальные переменные |
||
| 3274 | |||
| 3275 | Ко всем глобальным переменным следует писать комментарий о их назначении и |
||
| 3276 | (если не очевидно) почему они должны быть глобальными. Например: |
||
| 3277 | |||
| 3278 | ``` cpp |
||
| 3279 | // Общее количество тестов, прогоняемых в регрессионном тесте |
||
| 3280 | const int kNumTestCases = 6; |
||
| 3281 | ``` |
||
| 3282 | |||
| 3283 | ### Комментарии к реализации |
||
| 3284 | |||
| 3285 | Комментируйте реализацию функции или алгоритма в случае наличия неочевидных, |
||
| 3286 | интересных, важных кусков кода. |
||
| 3287 | |||
| 3288 | #### Описательные комментарии |
||
| 3289 | |||
| 3290 | Блоки кода, отличающиеся сложностью или нестандартностью, должны предваряться |
||
| 3291 | комментарием. |
||
| 3292 | |||
| 3293 | #### Комментарии к аргументам функций |
||
| 3294 | |||
| 3295 | Когда назначение аргумента функции неочевидно, стоит рассмотреть следующие |
||
| 3296 | варианты: |
||
| 3297 | * Если аргумент представляет собой фиксированное значение (literal constant), и |
||
| 3298 | он используется в разных блоках кода (и подразумевается, что его значение везде |
||
| 3299 | одно и то же), вам следует создать константу и явно использовать её. |
||
| 3300 | * По возможности измените сигнатуру функции для замены типа аргумента с bool на |
||
| 3301 | перечисление enum. Это сделает аргумент самодокументированным. |
||
| 3302 | * Для функций, использующих несколько конфигурационных опций в аргументах, |
||
| 3303 | можно создать отдельный класс (или структуру), объединяющий все опции. И |
||
| 3304 | передавать в функцию экземпляр этого класса. Такой подход имеет несколько |
||
| 3305 | преимуществ: опции обозначаются именами, что объясняет их назначение. |
||
| 3306 | Уменьшается количество аргументов в функции - код легче писать и читать. |
||
| 3307 | И если вам понадобится добавить ещё опций, менять сам вызов функции не придётся. |
||
| 3308 | * Вместо больших и сложных вложенных выражений используйте именованную |
||
| 3309 | переменную. |
||
| 3310 | * В крайнем случае используйте комментарии в месте вызова для прояснения |
||
| 3311 | назначения аргументов. |
||
| 3312 | |||
| 3313 | Рассмотрим примеры: |
||
| 3314 | ``` cpp |
||
| 3315 | // И какое назначение аргументов? |
||
| 3316 | const DecimalNumber product = calculateProduct(values, 7, false, nullptr); |
||
| 3317 | ``` |
||
| 3318 | Попробуем причесать: |
||
| 3319 | ``` cpp |
||
| 3320 | ProductOptions options; |
||
| 3321 | options.set_precision_decimals(7); |
||
| 3322 | options.set_use_cache(ProductOptions::kDontUseCache); |
||
| 3323 | const DecimalNumber product = |
||
| 3324 | CalculateProduct(values, options, /*completion_callback=*/nullptr); |
||
| 3325 | ``` |
||
| 3326 | |||
| 3327 | #### Чего делать точно нельзя |
||
| 3328 | |||
| 3329 | Не объясняйте очевидное. В частности, не описывайте дословно, что делает код, |
||
| 3330 | кроме случаев, когда его поведение неочевидно для читателя, |
||
| 3331 | хорошо разбирающегося в C++. Вместо этого, можно описать зачем этот код делает |
||
| 3332 | так (или вообще сделайте код самодокументированным). |
||
| 3333 | |||
| 3334 | Сравним: |
||
| 3335 | ``` cpp |
||
| 3336 | // Ищем элемент в векторе. <-- Плохо: очевидно же! |
||
| 3337 | auto iter = std::find(v.begin(), v.end(), element); |
||
| 3338 | if(iter != v.end()) { |
||
| 3339 | process(element); |
||
| 3340 | } |
||
| 3341 | ``` |
||
| 3342 | |||
| 3343 | С этим: |
||
| 3344 | ``` cpp |
||
| 3345 | // Обрабатывает (process) "element" пока есть хоть один |
||
| 3346 | auto iter = std::find(v.begin(), v.end(), element); |
||
| 3347 | if(iter != v.end()) { |
||
| 3348 | process(element); |
||
| 3349 | } |
||
| 3350 | ``` |
||
| 3351 | |||
| 3352 | Самодокументированный код вообще не нуждается в комментариях. |
||
| 3353 | Комментарий на код выше может быть вообще очевидным (и не нужным): |
||
| 3354 | ``` cpp |
||
| 3355 | if(!isAlreadyProcessed(element)) { |
||
| 3356 | process(element); |
||
| 3357 | } |
||
| 3358 | ``` |
||
| 3359 | |||
| 3360 | ### Пунктуация, орфография и грамматика |
||
| 3361 | |||
| 3362 | Обращайте внимание на пунктуацию, орфографию и грамматику: намного проще читать |
||
| 3363 | грамотно написанные комментарии. |
||
| 3364 | |||
| 3365 | Комментарии должны быть написаны как рассказ: с правильной расстановкой |
||
| 3366 | прописных букв и знаков препинания. В большинстве случаев законченные |
||
| 3367 | предложения легче понимаются, нежели обрывки фраз. Короткие комментарии, |
||
| 3368 | например как построчные, могут быть менее формальными, но всё равно должны |
||
| 3369 | следовать общему стилю. |
||
| 3370 | |||
| 3371 | Хотя излишнее внимание код-ревьюера к использованию запятых вместо точек с |
||
| 3372 | запятой может слегка раздражать, очень важно поддерживать высокий уровень |
||
| 3373 | читабельности и понятности кода. Правильная пунктуация, орфография и грамматика |
||
| 3374 | этому очень сильно способствует. |
||
| 3375 | |||
| 3376 | ### Комментарии `TODO` |
||
| 3377 | |||
| 3378 | Используйте комментарии `TODO` для временного кода или достаточно хорошего |
||
| 3379 | (промежуточного, не идеального) решения. |
||
| 3380 | |||
| 3381 | Комментарий должен включать строку `TODO` (все буквы прописные), за ней имя, |
||
| 3382 | адрес e-mail, ID дефекта или другая информация для идентификации разработчика и |
||
| 3383 | сущности проблемы, для которой написан `TODO`. Цель такого описания - |
||
| 3384 | возможность потом найти больше деталей. Наличие `TODO` с описанием не означает, |
||
| 3385 | что указанный программист исправит проблему. Поэтому, когда вы создаёте `TODO`, |
||
| 3386 | обычно там указано Ваше имя. |
||
| 3387 | |||
| 3388 | ``` cpp |
||
| 3389 | // TODO(kl@gmail.com): Используйте "*" для объединения. |
||
| 3390 | // TODO(Zeke) Изменить для связывания. |
||
| 3391 | // TODO(bug 12345): удалить функционал "Последний посетитель". |
||
| 3392 | ``` |
||
| 3393 | |||
| 3394 | Если ваш `TODO` имеет вид "В будущем сделаем по-другому", то указывайте либо |
||
| 3395 | конкретную дату (`Исправить в ноябре 2005`), либо событие (`Удалить тот код, |
||
| 3396 | когда все клиенты будут обрабатывать XML запросы`). |
||
| 3397 | |||
| 3398 | ## Форматирование |
||
| 3399 | |||
| 3400 | Стиль кодирования и форматирования являются вещью произвольной, однако проект |
||
| 3401 | намного легче управляется, если все следуют одному стилю. Хотя кто-то может не |
||
| 3402 | соглашаться со всеми правилами (или пользоваться тем, чем привыкли), очень важно |
||
| 3403 | чтобы все следовали единым правилам, чтобы легко читать и понимать чужой код. |
||
| 3404 | |||
| 3405 | Для корректного форматирования мы создали [[файл настроек для Qt Creator]]. |
||
| 3406 | |||
| 3407 | ### Длина строк |
||
| 3408 | |||
| 3409 | Длина строки кода не должна превышать 80 символов. |
||
| 3410 | |||
| 3411 | Это правило немного спорное, однако масса уже существующего кода придерживается |
||
| 3412 | этого принципа, и мы также поддерживаем его. |
||
| 3413 | |||
| 3414 | Приверженцы правила утверждают, что строки длиннее не нужны, а постоянно |
||
| 3415 | подгонять размеры окон утомительно. Кроме того, некоторые размещают окна с кодом |
||
| 3416 | рядом друг с другом и не могут произвольно увеличивать ширину окон. При этом |
||
| 3417 | ширина в 80 символов - исторический стандарт, зачем его менять?. |
||
| 3418 | |||
| 3419 | Другая сторона утверждает, что длинные строки могут улучшить читаемость кода. |
||
| 3420 | 80 символов - пережиток мэйнфреймов 1960-х. Современные экраны вполне могут |
||
| 3421 | показывать более длинные строки. |
||
| 3422 | |||
| 3423 | _Вывод:_ |
||
| 3424 | |||
| 3425 | 80 символов - максимум. |
||
| 3426 | Строка может превышать предел в 80 символов если это: |
||
| 3427 | |||
| 3428 | * комментарий при разделении потеряет в понятности или лёгкости копирования. |
||
| 3429 | Например, комментарий с примером команды или URL-ссылкой, длиннее 80 символов; |
||
| 3430 | * выражение с `include`; |
||
| 3431 | * [защита от повторного включения](#защита-от-повторного-включения); |
||
| 3432 | * `using`-декларация. |
||
| 3433 | |||
| 3434 | ### Не-ASCII символы |
||
| 3435 | |||
| 3436 | Не-ASCII символы следует использовать как можно реже, кодировка должна быть |
||
| 3437 | UTF-8. |
||
| 3438 | |||
| 3439 | Вы не должны помещать в код строки которые будут показываться пользователю |
||
| 3440 | (даже английские), поэтому Не-ASCII символы должны быть редкостью. Однако, |
||
| 3441 | в ряде случаев допустимо включать такие слова в код. Например, если код парсит |
||
| 3442 | файлы данных (с неанглийской кодировкой), возможно включать в код национальные |
||
| 3443 | слова-разделители. В более общем случае, код модульных тестов может содержать |
||
| 3444 | национальные строки. В этих случаях следует использовать кодировку UTF-8, т.к. |
||
| 3445 | она понятна большинству утилит (которые понимают не только ASCII). |
||
| 3446 | |||
| 3447 | Кодировка hex также допустима, особенно если она улучшает читаемость. |
||
| 3448 | Например, `"\xEF\xBB\xBF"` или `u8"\uFEFF"` - неразрывный пробел нулевой длины в |
||
| 3449 | *Unicode*, и который не должен отображаться в правильном UTF-8 тексте. |
||
| 3450 | |||
| 3451 | Используйте префикс `u8` чтобы литералы вида `\uXXXX` кодировались в UTF-8. |
||
| 3452 | Не используйте его для строк, содержащих не-ASCII символы уже закодированные в |
||
| 3453 | UTF-8 - можете получить корявый текст если компилятор не распознает исходный |
||
| 3454 | код как UTF-8. |
||
| 3455 | |||
| 3456 | Избегайте использования символов C++11 `char16_t` и `char32_t` т.к. |
||
| 3457 | они нужны для не-UTF-8 строк. По тем же причинам не используйте |
||
| 3458 | `wchar_t` (кроме случаев работы с Windows API, использующий wchar_t). |
||
| 3459 | |||
| 3460 | ### Пробелы против Табуляции |
||
| 3461 | |||
| 3462 | Используйте только пробелы для отступов. Размер отступа составляет 4 пробела. |
||
| 3463 | |||
| 3464 | Мы используем пробелы для отступов. Не используйте табуляцию в своём коде. Вам |
||
| 3465 | стоит настроить свой редактор на вставку 4 пробелов при нажатии клавиши `Tab`. |
||
| 3466 | |||
| 3467 | ### Объявления и определения функций |
||
| 3468 | |||
| 3469 | Тип возвращаемого значения, имя функции и её параметры должны быть размещены в |
||
| 3470 | одной строке, если всё умещается. Слишком длинный список параметров можно |
||
| 3471 | разбить на строки. |
||
| 3472 | |||
| 3473 | Пример правильного оформления функции: |
||
| 3474 | ``` cpp |
||
| 3475 | ReturnType ClassName::functionName(Type par_name1, Type par_name2) { |
||
| 3476 | doSomething(); |
||
| 3477 | ... |
||
| 3478 | } |
||
| 3479 | ``` |
||
| 3480 | В случае если одной строки мало: |
||
| 3481 | ``` cpp |
||
| 3482 | ReturnType ClassName::reallyLongFunctionName(Type par_name1, Type par_name2, |
||
| 3483 | Type par_name3) { |
||
| 3484 | doSomething(); |
||
| 3485 | ... |
||
| 3486 | } |
||
| 3487 | ``` |
||
| 3488 | или, если первый параметр также не помещается: |
||
| 3489 | ``` cpp |
||
| 3490 | ReturnType LongClassName::reallyReallyReallyLongFunctionName( |
||
| 3491 | Type par_name1, // Отступ 8 пробелов |
||
| 3492 | Type par_name2, |
||
| 3493 | Type par_name3) { |
||
| 3494 | doSomething(); // Отступ 4 пробела |
||
| 3495 | ... |
||
| 3496 | } |
||
| 3497 | ``` |
||
| 3498 | |||
| 3499 | Несколько замечаний: |
||
| 3500 | |||
| 3501 | * Выбирайте хорошие имена для параметров. |
||
| 3502 | * Имя параметра можно опустить, если он не используется в определении функции. |
||
| 3503 | * Если тип возвращаемого значения и имя функции не помещаются в одной строке, |
||
| 3504 | тип оставьте на одной строке, имя функции перенесите на следующую. В этом |
||
| 3505 | случае не делайте дополнительный отступ перед именем функции. |
||
| 3506 | * Открывающая круглая скобка всегда находится на одной строке с именем функции. |
||
| 3507 | * Не вставляйте пробелы между именем функции и открывающей круглой скобкой. |
||
| 3508 | * Не вставляйте пробелы между круглыми скобками и параметрами. |
||
| 3509 | * Открывающая фигурная скобка всегда в конце последней строки определения. |
||
| 3510 | Не переносите её на новую строку. |
||
| 3511 | * Закрывающая фигурная скобка располагается либо на отдельной строке, либо на |
||
| 3512 | той же строке, где и открывающая скобка. |
||
| 3513 | * Между закрывающей круглой скобкой и открывающей фигурной скобкой должен быть |
||
| 3514 | пробел. |
||
| 3515 | * Старайтесь выравнивать все параметры. |
||
| 3516 | * Стандартный отступ - 4 пробела. |
||
| 3517 | * При переносе параметров на другую строку используйте отступ в 8 пробелов. |
||
| 3518 | |||
| 3519 | Можно опустить имя неиспользуемых параметров, если это очевидно из контекста: |
||
| 3520 | ``` cpp |
||
| 3521 | class Foo { |
||
| 3522 | public: |
||
| 3523 | Foo(const Foo&) = delete; |
||
| 3524 | Foo& operator=(const Foo&) = delete; |
||
| 3525 | }; |
||
| 3526 | ``` |
||
| 3527 | |||
| 3528 | Неиспользуемые параметры с неочевидным контекстом следует закомментировать в |
||
| 3529 | определении функции: |
||
| 3530 | ``` cpp |
||
| 3531 | class Shape { |
||
| 3532 | public: |
||
| 3533 | virtual void rotate(double radians) = 0; |
||
| 3534 | }; |
||
| 3535 | |||
| 3536 | class Circle : public Shape { |
||
| 3537 | public: |
||
| 3538 | void rotate(double radians) override; |
||
| 3539 | }; |
||
| 3540 | |||
| 3541 | void Circle::rotate(double /*radians*/) {} |
||
| 3542 | ``` |
||
| 3543 | ``` cpp |
||
| 3544 | // Плохой стиль - если кто-то потом захочет изменить реализацию функции, |
||
| 3545 | // назначение параметра не ясно. |
||
| 3546 | void Circle::rotate(double) {} |
||
| 3547 | ``` |
||
| 3548 | |||
| 3549 | Атрибуты и макросы старайтесь использовать в начале объявления или определения |
||
| 3550 | функции, до типа возвращаемого значения: |
||
| 3551 | ``` cpp |
||
| 3552 | ABSL_MUST_USE_RESULT bool isOk(); |
||
| 3553 | ``` |
||
| 3554 | |||
| 3555 | ### Формат лямбда-выражений |
||
| 3556 | |||
| 3557 | Форматируйте параметры и тело выражения аналогично обычной функции, список |
||
| 3558 | захватываемых переменных - как обычный список. |
||
| 3559 | |||
| 3560 | Для захвата переменных по ссылке не ставьте пробел между амперсандом (`&`) и |
||
| 3561 | именем переменной. |
||
| 3562 | |||
| 3563 | ``` cpp |
||
| 3564 | int x = 0; |
||
| 3565 | auto x_plus_n = [&x](int n) -> int { return x + n; } |
||
| 3566 | ``` |
||
| 3567 | |||
| 3568 | Короткие лямбды можно использовать напрямую как аргумент функции. |
||
| 3569 | |||
| 3570 | ``` cpp |
||
| 3571 | std::set<int> blacklist = {7, 8, 9}; |
||
| 3572 | std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1}; |
||
| 3573 | digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) { |
||
| 3574 | return blacklist.find(i) != blacklist.end(); |
||
| 3575 | }), |
||
| 3576 | digits.end()); |
||
| 3577 | ``` |
||
| 3578 | |||
| 3579 | ### Числа с плавающей точкой |
||
| 3580 | |||
| 3581 | Числа с плавающей точкой всегда должны быть с десятичной точкой и числами по |
||
| 3582 | обе стороны от неё (даже в случае экспоненциальной нотации). Такой подход |
||
| 3583 | позволяет улучшить читаемость: все числа с плавающей запятой будут в |
||
| 3584 | одинаковом формате, не спутаешь с целым числом, и символы E/e экспоненциальной |
||
| 3585 | нотации не примешь за шестнадцатеричные цифры. Помните, что число в |
||
| 3586 | экспоненциальной нотации не является целым числом. |
||
| 3587 | |||
| 3588 | _Плохой пример:_ |
||
| 3589 | ``` cpp |
||
| 3590 | float f = 1.f; |
||
| 3591 | long double ld = -.5L; |
||
| 3592 | double d = 1248e6; |
||
| 3593 | ``` |
||
| 3594 | |||
| 3595 | _Хороший пример:_ |
||
| 3596 | ``` cpp |
||
| 3597 | float f = 1.0f; |
||
| 3598 | float f2 = 1; // Также правильно |
||
| 3599 | long double ld = -0.5L; |
||
| 3600 | double d = 1248.0e6; |
||
| 3601 | ``` |
||
| 3602 | |||
| 3603 | ### Вызов функции |
||
| 3604 | |||
| 3605 | Следует либо писать весь вызов функции одной строкой, либо размещать аргументы |
||
| 3606 | на новой строке. И отступ может быть либо по первому аргументу, либо 8 пробелов. |
||
| 3607 | Старайтесь минимизировать количество строк, размещайте по несколько аргументов |
||
| 3608 | на каждой строке. |
||
| 3609 | |||
| 3610 | Формат вызова функции: |
||
| 3611 | ``` cpp |
||
| 3612 | bool result = doSomething(argument1, argument2, argument3); |
||
| 3613 | ``` |
||
| 3614 | |||
| 3615 | Если аргументы не помещаются в одной строке, то следует разделить их на |
||
| 3616 | несколько строк, и каждая следующая строка выравнивается по первому аргументу. |
||
| 3617 | Не добавляйте пробелы между круглыми скобками и аргументами: |
||
| 3618 | |||
| 3619 | ``` cpp |
||
| 3620 | bool result = doSomething(averyveryveryverylongargument1, |
||
| 3621 | argument2, argument3); |
||
| 3622 | ``` |
||
| 3623 | |||
| 3624 | Допускается размещать аргументы на нескольких строках с отступом в 8 пробелов: |
||
| 3625 | ``` cpp |
||
| 3626 | if(...) { |
||
| 3627 | ... |
||
| 3628 | ... |
||
| 3629 | if(...) { |
||
| 3630 | bool result = doSomething( |
||
| 3631 | argument1, argument2, // Отступ 8 пробелов |
||
| 3632 | argument3, argument4); |
||
| 3633 | ... |
||
| 3634 | } |
||
| 3635 | } |
||
| 3636 | ``` |
||
| 3637 | |||
| 3638 | Старайтесь размещать по несколько аргументов в строке, уменьшая количество строк |
||
| 3639 | на вызов функции, если это не ухудшает читаемость. Некоторые считают, что |
||
| 3640 | форматирование строго по одному аргументу в строке более читаемо и облегчает |
||
| 3641 | редактирование аргументов. Однако, мы ориентируемся прежде всего на читателей |
||
| 3642 | кода (не редактирование), поэтому предлагаем ряд подходов для улучшения |
||
| 3643 | читаемости. |
||
| 3644 | |||
| 3645 | Если несколько аргументов в одной строке ухудшают читаемость(из-за сложности или |
||
| 3646 | запутанности выражений, попробуйте создать для аргументов "говорящие" |
||
| 3647 | переменные: |
||
| 3648 | |||
| 3649 | ``` cpp |
||
| 3650 | int my_heuristic = scores[x] * y + bases[x]; |
||
| 3651 | bool result = doSomething(my_heuristic, x, y, z); |
||
| 3652 | ``` |
||
| 3653 | |||
| 3654 | Или разместите сложный аргумент на отдельной строке и добавьте поясняющий |
||
| 3655 | комментарий: |
||
| 3656 | ``` cpp |
||
| 3657 | bool result = doSomething(scores[x] * y + bases[x], // Небольшая эвристика |
||
| 3658 | x, y, z); |
||
| 3659 | ``` |
||
| 3660 | |||
| 3661 | Если в вызове функции ещё есть аргументы, которые желательно разместить на |
||
| 3662 | отдельной строке - размещайте. Решение должно основываться улучшении |
||
| 3663 | читаемость кода. |
||
| 3664 | |||
| 3665 | Иногда аргументы формируют структуру. В этом случае форматируйте аргументы |
||
| 3666 | согласно требуемой структуре: |
||
| 3667 | |||
| 3668 | ``` cpp |
||
| 3669 | // Преобразование с помощью матрицы 3x3 |
||
| 3670 | my_widget.transform(x1, x2, x3, |
||
| 3671 | y1, y2, y3, |
||
| 3672 | z1, z2, z3); |
||
| 3673 | ``` |
||
| 3674 | |||
| 3675 | ### Форматирование списка инициализации |
||
| 3676 | |||
| 3677 | Форматируйте список инициализации аналогично вызову функции. |
||
| 3678 | |||
| 3679 | Если список в скобках следует за именем (например, имя типа или переменной), |
||
| 3680 | форматируйте {} как будто это вызов функции с этим именем. Даже если имени нет, |
||
| 3681 | считайте что оно есть, только пустое. |
||
| 3682 | |||
| 3683 | ``` cpp |
||
| 3684 | // Пример списка инициализации на одной строке. |
||
| 3685 | return {foo, bar}; |
||
| 3686 | functioncall({foo, bar}); |
||
| 3687 | std::pair<int, int> p{foo, bar}; |
||
| 3688 | |||
| 3689 | // Когда хочется разделить на строки. |
||
| 3690 | someFunction( |
||
| 3691 | {"assume a zero-length name before {"}, |
||
| 3692 | some_other_function_parameter); |
||
| 3693 | |||
| 3694 | SomeType variable{ |
||
| 3695 | some, other, values, |
||
| 3696 | {"assume a zero-length name before {"}, |
||
| 3697 | SomeOtherType{ |
||
| 3698 | "Very long string requiring the surrounding breaks.", |
||
| 3699 | some, other values}, |
||
| 3700 | SomeOtherType{"Slightly shorter string", |
||
| 3701 | some, other, values}}; |
||
| 3702 | |||
| 3703 | SomeType variable{ |
||
| 3704 | "This is too long to fit all in one line"}; |
||
| 3705 | |||
| 3706 | MyType m = { // Here, you could also break before {. |
||
| 3707 | superlongvariablename1, |
||
| 3708 | superlongvariablename2, |
||
| 3709 | {short, interior, list}, |
||
| 3710 | {interiorwrappinglist, |
||
| 3711 | interiorwrappinglist2}}; |
||
| 3712 | ``` |
||
| 3713 | |||
| 3714 | ### Условия |
||
| 3715 | |||
| 3716 | В выражении `if`, включая дополнительные конструкции `else if` и `else`, пробел |
||
| 3717 | ставится только между закрывающей круглой скобкой и открывающей фигурной. Между |
||
| 3718 | круглыми скобками и их содержимым скобки не допускаются. Открывающая фигурная |
||
| 3719 | скобка всегда отделяется пробелом от других конструкций в этой же строке. |
||
| 3720 | |||
| 3721 | ``` cpp |
||
| 3722 | if(condition) { // без пробелов внутри скобок |
||
| 3723 | ... // отступ 4 пробела |
||
| 3724 | } |
||
| 3725 | else if(...) { // 'else' находится на новой строке после закрывающей скобки |
||
| 3726 | ... |
||
| 3727 | } |
||
| 3728 | else { |
||
| 3729 | ... |
||
| 3730 | } |
||
| 3731 | ``` |
||
| 3732 | ``` cpp |
||
| 3733 | if(condition) { // Хороший код - нет пробела после 'if' и один пробел перед { |
||
| 3734 | if(condition){ // Плохо - нет пробела перед { |
||
| 3735 | if (condition) { // Плохо - пробел после 'if' |
||
| 3736 | if (condition){ // Дважды плохо |
||
| 3737 | ``` |
||
| 3738 | |||
| 3739 | Даже если у выражения `if` нет последующих `else if` или `else`, а сама |
||
| 3740 | конструкция в итоге может уместиться на одной либо максимум двух строках, |
||
| 3741 | исключать фигурные скобки из конструкции нежелательно. |
||
| 3742 | |||
| 3743 | Следующие примеры показывают, как делать _нельзя_: |
||
| 3744 | ``` cpp |
||
| 3745 | // Плохо - условие в одну строку, хотя есть 'else' |
||
| 3746 | if(x) doThis(); |
||
| 3747 | else doThat(); |
||
| 3748 | |||
| 3749 | // Плохо - в конструкции IF присутствует ELSE, а скобки не везде |
||
| 3750 | if(condition) |
||
| 3751 | foo; |
||
| 3752 | else { |
||
| 3753 | bar; |
||
| 3754 | } |
||
| 3755 | |||
| 3756 | // Плохо - конструкция IF слишком длинная, чтобы исключить фигурные скобки |
||
| 3757 | if(condition) |
||
| 3758 | // Comment |
||
| 3759 | doSomething(); |
||
| 3760 | |||
| 3761 | // Плохо - и условие разбито на несколько строк, |
||
| 3762 | // и конструкция длинная, а скобок нет |
||
| 3763 | if(condition1 && |
||
| 3764 | condition2) |
||
| 3765 | DoSomething(); |
||
| 3766 | ``` |
||
| 3767 | |||
| 3768 | ### Циклы и `switch`-`case` |
||
| 3769 | |||
| 3770 | Конструкция `switch` может использовать скобки для блоков. Описывайте |
||
| 3771 | нетривиальные переходы между вариантами. Скобки необязательны для циклов с одним |
||
| 3772 | выражением. Пустой цикл должен использовать либо пустое тело в скобках или |
||
| 3773 | `continue`. |
||
| 3774 | |||
| 3775 | Блоки `case` в `switch` могут как быть с фигурными скобками, так быть и без них |
||
| 3776 | (на ваш выбор). Если же скобки используются, используйте формат, описанный ниже. |
||
| 3777 | |||
| 3778 | Рекомендуется в `switch` делать секцию `default`. Это необязательно в случае |
||
| 3779 | использования перечисления, да и компилятор может выдать предупреждение если |
||
| 3780 | обработаны не все значения. Если секция `default` не должна выполняться, тогда |
||
| 3781 | формируйте это как ошибку. Например: |
||
| 3782 | |||
| 3783 | ``` cpp |
||
| 3784 | switch(var) { |
||
| 3785 | case 0: { // Отступ 4 пробела |
||
| 3786 | ... // Отступ 8 пробелов |
||
| 3787 | break; |
||
| 3788 | } |
||
| 3789 | case 1: { |
||
| 3790 | ... |
||
| 3791 | break; |
||
| 3792 | } |
||
| 3793 | default: { |
||
| 3794 | assert(false); |
||
| 3795 | } |
||
| 3796 | } |
||
| 3797 | ``` |
||
| 3798 | |||
| 3799 | Переход с одной метки на следующую должен быть помечен атрибутом |
||
| 3800 | `[[fallthrough]];`. Размещайте `[[fallthrough]];` в точке, где будет переход. |
||
| 3801 | Исключение из этого правила - последовательные метки без кода, в этом случае |
||
| 3802 | помечать ничего не нужно. |
||
| 3803 | |||
| 3804 | ``` cpp |
||
| 3805 | switch(x) { |
||
| 3806 | case 41: // Без пометок |
||
| 3807 | case 43: |
||
| 3808 | if(dont_be_picky) { |
||
| 3809 | // Используйте атрибут вместо (или совместно) с комментарием о |
||
| 3810 | // переходе |
||
| 3811 | [[fallthrough]]; |
||
| 3812 | } |
||
| 3813 | else { |
||
| 3814 | closeButNoCigar(); |
||
| 3815 | break; |
||
| 3816 | } |
||
| 3817 | case 42: |
||
| 3818 | doSomethingSpecial(); |
||
| 3819 | [[fallthrough]]; |
||
| 3820 | default: |
||
| 3821 | doSomethingGeneric(); |
||
| 3822 | break; |
||
| 3823 | } |
||
| 3824 | ``` |
||
| 3825 | |||
| 3826 | Скобки являются опциональными для циклов с одной операцией, однако лучше всегда |
||
| 3827 | их использовать. |
||
| 3828 | |||
| 3829 | ``` cpp |
||
| 3830 | for(int i = 0; i < kSomeNumber; ++i) |
||
| 3831 | printf("I love you\n"); |
||
| 3832 | |||
| 3833 | for(int i = 0; i < kSomeNumber; ++i) { |
||
| 3834 | printf("I take it back\n"); |
||
| 3835 | } |
||
| 3836 | ``` |
||
| 3837 | |||
| 3838 | Пустой цикл должен быть оформлен либо как пара скобок, либо как continue без |
||
| 3839 | скобок. Не используйте одиночную точку с запятой. |
||
| 3840 | |||
| 3841 | ``` cpp |
||
| 3842 | while(condition) { |
||
| 3843 | // Повторять до получения false |
||
| 3844 | } |
||
| 3845 | |||
| 3846 | for(int i = 0; i < kSomeNumber; ++i) {} // Хорошо. Если разбить на две |
||
| 3847 | // строки - тоже будет хорошо |
||
| 3848 | |||
| 3849 | while(condition) continue; // Хорошо - continue указывает на отсутствие |
||
| 3850 | // дополнительной логики |
||
| 3851 | |||
| 3852 | while(condition); // Плохо - выглядит как часть цикла do/while |
||
| 3853 | ``` |
||
| 3854 | |||
| 3855 | ### Указатели и ссылки |
||
| 3856 | |||
| 3857 | Не ставьте пробелы вокруг `'.'` и `'->'`. Оператор разыменования или взятия |
||
| 3858 | адреса должен быть без пробелов. |
||
| 3859 | |||
| 3860 | Ниже приведены примеры правильного форматирования выражений с указателями и |
||
| 3861 | ссылками: |
||
| 3862 | |||
| 3863 | ``` cpp |
||
| 3864 | x = *p; |
||
| 3865 | p = &x; |
||
| 3866 | x = r.y; |
||
| 3867 | x = r->y; |
||
| 3868 | ``` |
||
| 3869 | |||
| 3870 | Отметим: |
||
| 3871 | |||
| 3872 | * `'.'` и `'->'` используются без пробелов. |
||
| 3873 | * Операторы `*` или `&` не отделяются пробелами. |
||
| 3874 | |||
| 3875 | При объявлении переменной или аргумента можно размещать '*' как к типу, так и к |
||
| 3876 | имени: |
||
| 3877 | |||
| 3878 | ``` cpp |
||
| 3879 | // Отлично, пробел до *, & |
||
| 3880 | char *c; |
||
| 3881 | const std::string &str; |
||
| 3882 | |||
| 3883 | // Отлично, пробел после *, & |
||
| 3884 | char* c; |
||
| 3885 | const std::string& str; |
||
| 3886 | ``` |
||
| 3887 | |||
| 3888 | Старайтесь использовать единый стиль в файле кода, при модификации существующего |
||
| 3889 | файла применяйте используемое форматирование. |
||
| 3890 | |||
| 3891 | Допускается объявлять несколько переменных одним выражением. Однако не |
||
| 3892 | используйте множественное объявление с указателями или ссылками - это может быть |
||
| 3893 | неправильно понято. |
||
| 3894 | |||
| 3895 | ``` cpp |
||
| 3896 | // Хорошо - читаемо |
||
| 3897 | int x, y; |
||
| 3898 | |||
| 3899 | int x, *y; // Плохо - не используйте множественное объявление с & или * |
||
| 3900 | char * c; // Плохо - пробелы с обеих сторон * |
||
| 3901 | const std::string & str; // Плохо - пробелы с обеих сторон & |
||
| 3902 | ``` |
||
| 3903 | |||
| 3904 | ### Логические выражения |
||
| 3905 | |||
| 3906 | Если логическое выражение превышает |
||
| 3907 | [рекомендованную длину строки](#длина-строк), используйте единый подход к |
||
| 3908 | разбивке выражения на строки. |
||
| 3909 | |||
| 3910 | В данном примере, логический оператор `&&` находится всегда в конце строки: |
||
| 3911 | ``` cpp |
||
| 3912 | if(this_one_thing > this_other_thing && |
||
| 3913 | a_third_thing == a_fourth_thing && |
||
| 3914 | yet_another && last_one) { |
||
| 3915 | ... |
||
| 3916 | } |
||
| 3917 | ``` |
||
| 3918 | |||
| 3919 | Отметим, что разбиение кода (согласно примеру) производится так, чтобы оператор |
||
| 3920 | `&&` завершал строку. Такой стиль чаще используется в коде Google, хотя |
||
| 3921 | расположение операторов в начале строки тоже допустимо. Также, можете добавлять |
||
| 3922 | дополнительные скобки для улучшения читабельности. Учтите, что использование |
||
| 3923 | операторов в виде пунктуации (такие как `&&` и `~`) более предпочтительно, что |
||
| 3924 | использование операторов в виде слов `and` и `compl`. |
||
| 3925 | |||
| 3926 | ### Возвращаемые значения |
||
| 3927 | |||
| 3928 | Нет нужды заключать выражения `return` в скобки. |
||
| 3929 | Используйте скобки в `return expr;` только если бы вы использовали их в |
||
| 3930 | выражении вида `x = expr;`. |
||
| 3931 | |||
| 3932 | ``` cpp |
||
| 3933 | return result; // Простое выражение - нет скобок |
||
| 3934 | |||
| 3935 | // Скобки - Ок. Они улучшают читаемость выражения |
||
| 3936 | return (some_long_condition && |
||
| 3937 | another_condition); |
||
| 3938 | |||
| 3939 | return (value); // Плохо. Например, вы бы не стали писать var = (value); |
||
| 3940 | return(result); // Плохо. return - это не функция! |
||
| 3941 | ``` |
||
| 3942 | |||
| 3943 | ### Инициализация переменных и массивов |
||
| 3944 | |||
| 3945 | Вы можете использовать `=`, `()` и `{}` на ваш выбор. |
||
| 3946 | |||
| 3947 | Следующие примеры корректны: |
||
| 3948 | |||
| 3949 | ``` cpp |
||
| 3950 | int x = 3; |
||
| 3951 | int x(3); |
||
| 3952 | int x{3}; |
||
| 3953 | std::string name = "Some Name"; |
||
| 3954 | std::string name("Some Name"); |
||
| 3955 | std::string name{"Some Name"}; |
||
| 3956 | ``` |
||
| 3957 | |||
| 3958 | Будьте внимательны при использовании списка инициализации `{...}` для типа, у |
||
| 3959 | которого есть конструктор с `std::initializer_list`. Компилятор предпочтёт |
||
| 3960 | использовать конструктор `std::initializer_list` при наличии списка в фигурных |
||
| 3961 | скобках. Заметьте, что пустые фигурные скобки `{}` - это особый случай и будет |
||
| 3962 | вызван конструктор по-умолчанию (если он доступен). Для явного использования |
||
| 3963 | конструктора без `std::initializer_list` применяйте круглые скобки вместо |
||
| 3964 | фигурных. |
||
| 3965 | |||
| 3966 | ``` cpp |
||
| 3967 | std::vector<int> v(100, 1); // Вектор из сотни единиц |
||
| 3968 | std::vector<int> v{100, 1}; // Вектор из 2-х элементов: 100 и 1 |
||
| 3969 | ``` |
||
| 3970 | |||
| 3971 | Также использование круглых скобок запрещает ряд преобразований целых типов |
||
| 3972 | (преобразования с уменьшением точности). И можно получить ошибки компиляции, |
||
| 3973 | что весьма удобно. |
||
| 3974 | |||
| 3975 | ``` cpp |
||
| 3976 | int pi(3.14); // Ок: pi == 3 |
||
| 3977 | int pi{3.14}; // Ошибка компиляции: "сужающее" преобразование |
||
| 3978 | ``` |
||
| 3979 | |||
| 3980 | ### Директивы препроцессора |
||
| 3981 | |||
| 3982 | Знак `#` (признак директивы препроцессора) должен находиться всегда в начале |
||
| 3983 | строки. |
||
| 3984 | |||
| 3985 | Даже если директива препроцессора относится к вложенному коду, директивы пишутся |
||
| 3986 | с начала строки. |
||
| 3987 | |||
| 3988 | ``` cpp |
||
| 3989 | // Хорошо - директивы с начала строки |
||
| 3990 | if(lopsided_score) { |
||
| 3991 | #if DISASTER_PENDING // Корректно - начинается с начала строки |
||
| 3992 | dropEverything(); |
||
| 3993 | # if NOTIFY // Пробелы после # - ок, но не обязательно |
||
| 3994 | notifyClient(); |
||
| 3995 | # endif |
||
| 3996 | #endif |
||
| 3997 | backToNormal(); |
||
| 3998 | } |
||
| 3999 | ``` |
||
| 4000 | ``` cpp |
||
| 4001 | // Плохо - директивы с отступами |
||
| 4002 | if(lopsided_score) { |
||
| 4003 | #if DISASTER_PENDING // Неправильно! "#if" должна быть в начале строки |
||
| 4004 | dropEverything(); |
||
| 4005 | #endif // Неправильно! Не делайте отступ для "#endif" |
||
| 4006 | backToNormal(); |
||
| 4007 | } |
||
| 4008 | ``` |
||
| 4009 | |||
| 4010 | ### Форматирование классов |
||
| 4011 | |||
| 4012 | Размещайте секции в следующем порядке: `public`, `protected` и `private`. |
||
| 4013 | Отступ не требуется. |
||
| 4014 | |||
| 4015 | Ниже описан базовый формат для класса (за исключением комментариев, см. |
||
| 4016 | [Комментарии класса](#комментарии-класса)): |
||
| 4017 | ``` cpp |
||
| 4018 | class MyClass : public OtherClass |
||
| 4019 | { |
||
| 4020 | public: // без отступов |
||
| 4021 | MyClass(); // Обычный 4-х пробельный отступ |
||
| 4022 | explicit MyClass(int var); |
||
| 4023 | ~MyClass() {} |
||
| 4024 | |||
| 4025 | void someFunction(); |
||
| 4026 | void someFunctionThatDoesNothing() { |
||
| 4027 | } |
||
| 4028 | |||
| 4029 | void set_some_var(int var) { m_some_var = var; } |
||
| 4030 | int some_var() const { return m_some_var; } |
||
| 4031 | |||
| 4032 | private: |
||
| 4033 | bool someInternalFunction(); |
||
| 4034 | |||
| 4035 | int m_some_var; |
||
| 4036 | int m_some_other_var; |
||
| 4037 | }; |
||
| 4038 | ``` |
||
| 4039 | |||
| 4040 | _Замечания:_ |
||
| 4041 | |||
| 4042 | * Имя базового класса пишется в той же строке, что и имя наследуемого класса |
||
| 4043 | (конечно, с учётом ограничения в 80 символов). |
||
| 4044 | * Ключевые слова `public:`, `protected:`, и `private:` расположены без |
||
| 4045 | каких-либо отступов. |
||
| 4046 | * Перед каждым из этих ключевых слов должна быть пустая строка (за исключением |
||
| 4047 | первого упоминания). Также в маленьких классах пустые строки можно опустить. |
||
| 4048 | * Не добавляйте пустую строку после этих ключевых слов. |
||
| 4049 | * Секция `public` должна быть первой, за ней `protected` и в конце секция `private`. |
||
| 4050 | * Порядок объявлений в каждой из этих секций рассмотрен |
||
| 4051 | [тут (порядок объявления)](#порядок-объявления). |
||
| 4052 | |||
| 4053 | ### Список инициализации конструктора |
||
| 4054 | |||
| 4055 | Списки инициализации конструктора могут быть как в одну строку, так и на |
||
| 4056 | нескольких строках с 8-ми пробельным отступом. |
||
| 4057 | |||
| 4058 | Ниже представлены правильные форматы для списков инициализации: |
||
| 4059 | ``` cpp |
||
| 4060 | // Всё в одну строку |
||
| 4061 | MyClass::MyClass(int var) : some_var_(var) { |
||
| 4062 | doSomething(); |
||
| 4063 | } |
||
| 4064 | |||
| 4065 | // Если сигнатура и список инициализации не помещается на одной строке, |
||
| 4066 | // нужно перенести двоеточие и всё что после него на новую строку |
||
| 4067 | MyClass::MyClass(int var) |
||
| 4068 | : some_var_(var), some_other_var_(var + 1) { |
||
| 4069 | doSomething(); |
||
| 4070 | } |
||
| 4071 | |||
| 4072 | // Если список занимает несколько строк, то размещайте каждый элемент на |
||
| 4073 | // отдельной строке и всё выравниваем |
||
| 4074 | MyClass::MyClass(int var) |
||
| 4075 | : some_var_(var), // Отступ 8 пробелов |
||
| 4076 | some_other_var_(var + 1) { // Выравнивание по предыдущему |
||
| 4077 | doSomething(); |
||
| 4078 | } |
||
| 4079 | |||
| 4080 | // Как и в других случаях, фигурные скобки могут размещаться на одной строке |
||
| 4081 | MyClass::MyClass(int var) |
||
| 4082 | : some_var_(var) {} |
||
| 4083 | ``` |
||
| 4084 | |||
| 4085 | ### Форматирование пространств имён |
||
| 4086 | |||
| 4087 | Содержимое в пространстве имён пишется без отступа. |
||
| 4088 | |||
| 4089 | [Пространство имён](#пространства-имен) не добавляет отступов. Например: |
||
| 4090 | |||
| 4091 | ``` cpp |
||
| 4092 | namespace { |
||
| 4093 | |||
| 4094 | void foo() { // Хорошо. Без дополнительного отступа |
||
| 4095 | ... |
||
| 4096 | } |
||
| 4097 | |||
| 4098 | } // namespace |
||
| 4099 | ``` |
||
| 4100 | |||
| 4101 | Не делайте отступов в пространстве имён: |
||
| 4102 | |||
| 4103 | ``` cpp |
||
| 4104 | namespace { |
||
| 4105 | |||
| 4106 | // Плохо. Сделан отступ там, где не нужно |
||
| 4107 | void foo() { |
||
| 4108 | ... |
||
| 4109 | } |
||
| 4110 | |||
| 4111 | } // namespace |
||
| 4112 | ``` |
||
| 4113 | |||
| 4114 | При объявлении вложенных пространств имён, размещайте каждое объявление на |
||
| 4115 | отдельной строке. |
||
| 4116 | |||
| 4117 | ``` cpp |
||
| 4118 | namespace foo { |
||
| 4119 | namespace bar { |
||
| 4120 | ``` |
||
| 4121 | |||
| 4122 | Можно использовать новшество из C++17: |
||
| 4123 | |||
| 4124 | ``` cpp |
||
| 4125 | namespace foo::bar { |
||
| 4126 | } |
||
| 4127 | ``` |
||
| 4128 | |||
| 4129 | ### Горизонтальная разбивка |
||
| 4130 | |||
| 4131 | Используйте горизонтальную разбивку в зависимости от ситуации. Никогда не |
||
| 4132 | добавляйте пробелы в конец строки. |
||
| 4133 | |||
| 4134 | #### Общие принципы |
||
| 4135 | ``` cpp |
||
| 4136 | void f(bool b) { // Перед открывающей фигурной скобкой всегда ставьте пробел |
||
| 4137 | ... |
||
| 4138 | int i = 0; // Обычно перед точкой с запятой нет пробела |
||
| 4139 | // Пробелы внутри фигурных скобок для списка инициализации можно |
||
| 4140 | // добавлять на ваш выбор. |
||
| 4141 | // Если вы добавляете пробелы, то ставьте их с обеих сторон |
||
| 4142 | int x[] = { 0 }; |
||
| 4143 | int x[] = {0}; |
||
| 4144 | |||
| 4145 | // Пробелы вокруг двоеточия в списках наследования и инициализации |
||
| 4146 | class Foo : public Bar |
||
| 4147 | { |
||
| 4148 | public: |
||
| 4149 | // Для inline-функции добавляйте |
||
| 4150 | // пробелы внутри фигурных скобок (кроме пустого блока) |
||
| 4151 | Foo(int b) : Bar(), m_baz(b) {} // Пустой блок без пробелов |
||
| 4152 | void reset() { m_baz = 0; } // Пробелы разделяют фигурные скобки и реализацию |
||
| 4153 | ... |
||
| 4154 | ``` |
||
| 4155 | |||
| 4156 | Добавление разделительных пробелов может мешать при слиянии кода. Поэтому не |
||
| 4157 | добавляйте разделительных пробелов в существующий код. Вы можете удалить |
||
| 4158 | пробелы, если уже модифицировали эту строку. Или сделайте это отдельной |
||
| 4159 | операцией (предпочтительно, чтобы с этим кодом при этом никто не работал). |
||
| 4160 | |||
| 4161 | #### Циклы и условия |
||
| 4162 | ``` cpp |
||
| 4163 | if(b) { // Пробел после ключевого слова в условии или цикле |
||
| 4164 | } |
||
| 4165 | else { // пробел после else |
||
| 4166 | } |
||
| 4167 | |||
| 4168 | while(test) {} // Внутри круглых скобок обычно не ставят пробел |
||
| 4169 | |||
| 4170 | switch(i) { |
||
| 4171 | for(int i = 0; i < 5; ++i) { |
||
| 4172 | |||
| 4173 | // Циклы и условия могут могут внутри быть с пробелам. Но это редкость. |
||
| 4174 | // В любом случае, будьте последовательны |
||
| 4175 | switch( i ) { |
||
| 4176 | if( test ) { |
||
| 4177 | for( int i = 0; i < 5; ++i ) { |
||
| 4178 | |||
| 4179 | // В циклах после точки с запятой всегда ставьте пробел |
||
| 4180 | // Также некоторые любят ставить пробел и перед точкой с запятой, |
||
| 4181 | // но это редкость |
||
| 4182 | for( ; i < 5 ; ++i) { |
||
| 4183 | ... |
||
| 4184 | |||
| 4185 | // В циклы по диапазону всегда ставьте пробел до двоеточия и после |
||
| 4186 | for(auto x : counts) { |
||
| 4187 | ... |
||
| 4188 | } |
||
| 4189 | |||
| 4190 | switch(i) { |
||
| 4191 | case 1: // Перед двоеточием в case нет пробела |
||
| 4192 | ... |
||
| 4193 | case 2: break; // После двоеточия есть пробел, |
||
| 4194 | // если дальше на той же строке идёт код |
||
| 4195 | ``` |
||
| 4196 | |||
| 4197 | #### Операторы |
||
| 4198 | ``` cpp |
||
| 4199 | // Операторы присваивания всегда окружайте пробелами |
||
| 4200 | x = 0; |
||
| 4201 | |||
| 4202 | // Другие бинарные операторы обычно окружаются пробелами, |
||
| 4203 | // хотя допустимо умножение/деление записывать без пробелов. |
||
| 4204 | // Между выражением внутри скобок и самими скобками не вставляйте пробелы |
||
| 4205 | v = w * x + y / z; |
||
| 4206 | v = w*x + y/z; |
||
| 4207 | v = w * (x + z); |
||
| 4208 | |||
| 4209 | // Унарные операторы не отделяйте от их аргумента |
||
| 4210 | x = -5; |
||
| 4211 | ++x; |
||
| 4212 | if (x && !y) |
||
| 4213 | ... |
||
| 4214 | ``` |
||
| 4215 | |||
| 4216 | #### Шаблоны и приведение типов |
||
| 4217 | ``` cpp |
||
| 4218 | // Не ставьте пробелы внутри угловых скобок (< и >), |
||
| 4219 | // перед <, между >( в приведении |
||
| 4220 | std::vector<std::string> x; |
||
| 4221 | y = static_cast<char*>(x); |
||
| 4222 | |||
| 4223 | // Пробелы между типом и знаком указателя вполне допустимы. |
||
| 4224 | // Но смотрите на уже используемый формат кода |
||
| 4225 | std::vector<char *> x; |
||
| 4226 | ``` |
||
| 4227 | |||
| 4228 | ### Вертикальная разбивка |
||
| 4229 | |||
| 4230 | Сведите к минимуму вертикальное разбиение. |
||
| 4231 | |||
| 4232 | Это больше принцип, нежели правило: не добавляйте пустых строк без особой |
||
| 4233 | надобности. В частности, ставьте не больше 1-2 пустых строк между функциями, не |
||
| 4234 | начинайте функцию с пустой строки, не заканчивайте функцию пустой строкой, и |
||
| 4235 | старайтесь поменьше использовать пустые строки. Пустая строка в блоке кода |
||
| 4236 | должна работать как параграф в романе: визуально разделять две идеи. |
||
| 4237 | |||
| 4238 | _Базовый принцип:_ чем больше кода поместится на одном экране, тем легче его |
||
| 4239 | понять и отследить последовательность выполнения. Используйте пустую строку |
||
| 4240 | исключительно с целью визуально разделить эту последовательность. |
||
| 4241 | |||
| 4242 | Несколько полезных замечаний о пустых строках: |
||
| 4243 | |||
| 4244 | * Пустая строка в начале или в конце функции не улучшит читаемость. |
||
| 4245 | * Пустые строки в цепочке блоков `if-else` могут улучшить читаемость. |
||
| 4246 | * Пустая строка перед строкой с комментарием обычно помогает читаемости кода - |
||
| 4247 | новый комментарий обычно предполагает завершение старой мысли и начало новой |
||
| 4248 | идеи. И пустая строка явно на это намекает. |
||
| 4249 | * Пустые строки сразу после объявления пространства имён или блока пространств |
||
| 4250 | имён может улучшить читаемость за счёт визуального разделения более значимого |
||
| 4251 | содержимого от организационных обёрток. В особенности, когда первое объявление |
||
| 4252 | внутри пространства предваряется комментарием, это становится особым случаем |
||
| 4253 | предыдущего правила, помогая "прицепить" комментарий к последующему объявлению. |
||
| 4254 | |||
| 4255 | ## Исключения из правил |
||
| 4256 | |||
| 4257 | Соглашения по кодированию, описанные выше являются обязательными. Однако, как |
||
| 4258 | и в любых правилах, иногда в них есть исключения, которые сейчас и обсудим. |
||
| 4259 | |||
| 4260 | ### Существующий код, не соответствующий стилю |
||
| 4261 | |||
| 4262 | Допустимо отклоняться от правил, если производится работа с кодом, не |
||
| 4263 | соответствующим этому руководству. |
||
| 4264 | |||
| 4265 | Если модифицируется код, написанный другим стилем, допустимо отклоняться от |
||
| 4266 | требований этого руководства и использовать "местный" стиль, чтобы получить |
||
| 4267 | согласованный код. Если сомневаетесь - спросите автора кода (или того, кто это |
||
| 4268 | поддерживает). Помните, что *согласованность* включает также и текущий стиль |
||
| 4269 | кода. |
||
| 4270 | |||
| 4271 | ### Программирование под Windows |
||
| 4272 | |||
| 4273 | Программисты под Windows могут использовать особенный набор соглашений о |
||
| 4274 | кодировании, основанный на стиле заголовочных файлов в Windows и другом коде от |
||
| 4275 | Microsoft. Так как хочется сделать, чтобы код был понятным для всех, то |
||
| 4276 | рекомендуется использовать единое руководство по стилю в C++, одинаковое для |
||
| 4277 | всех платформ. |
||
| 4278 | |||
| 4279 | Повторим несколько рекомендаций, которые отличают данное руководство от стиля |
||
| 4280 | Windows: |
||
| 4281 | |||
| 4282 | * Не используйте венгерскую нотацию (например, именование целочисленной |
||
| 4283 | переменной как `iNum`). Вместо этого используйте соглашения об именовании от |
||
| 4284 | Google, включая расширение `.cpp` для файлов с исходным кодом. |
||
| 4285 | * Windows определяет собственные синонимы для базовых типов, такие как `DWORD`, |
||
| 4286 | `HANDLE` и др. Понятно, что при вызове Windows API рекомендуется использовать |
||
| 4287 | именно их. И всё равно, старайтесь определять типы, максимально похожие на C++. |
||
| 4288 | Например, используйте `const TCHAR *` вместо `LPCTSTR`. |
||
| 4289 | * При компиляции кода с помощью Microsoft Visual C++ установите уровень |
||
| 4290 | предупреждений 3 или выше. Также установите настройку, чтобы трактовать все |
||
| 4291 | предупреждения как ошибки. |
||
| 4292 | * Вообще, не используйте нестандартные расширения, такие как `#pragma` и |
||
| 4293 | `__declspec` (исключение для случаев крайней необходимости). Использование |
||
| 4294 | `__declspec(dllimport)` и `__declspec(dllexport)` допустимо, однако следует |
||
| 4295 | оформить их как макросы `DLLIMPORT` и `DLLEXPORT`: в этом случае их можно легко |
||
| 4296 | заблокировать, если код будет распространяться. |
||
| 4297 | |||
| 4298 | С другой стороны, есть правила, которые можно нарушать при программировании под |
||
| 4299 | Windows: |
||
| 4300 | |||
| 4301 | * Обычно рекомендуется не использовать |
||
| 4302 | [множественное наследование реализации](#наследование); однако это требуется |
||
| 4303 | при использовании COM и некоторых классов ATL/WTL. В этом случае (при реализации |
||
| 4304 | COM или ATL/WTL) нарушение правила допустимо. |
||
| 4305 | * Хотя использование исключений в собственном коде не рекомендуется, они |
||
| 4306 | интенсивно используются в ATL и некоторых STL (в том числе и в варианте |
||
| 4307 | библиотеки от Visual C++). |
||
| 4308 | * Типичный способ работы с |
||
| 4309 | *прекомпилированными заголовочными файлами (precompiled headers)* - включить |
||
| 4310 | такой файл первым в каждый файл исходников, обычно с именем `StdAfx.h` или |
||
| 4311 | `precompile.h`. Чтобы не создавать проблем при распространении кода, лучше |
||
| 4312 | избегать явного включения такого файла (за исключением `precompile.cpp`). |
||
| 4313 | Используйте опцию компилятора /FI для автоматического включения такого файла. |
||
| 4314 | * Заголовочный файлы ресурсов (обычно `resource.h`), содержащий только макросы, |
||
| 4315 | может не следовать рекомендациям этого руководства. |