forked from google/styleguide
-
Notifications
You must be signed in to change notification settings - Fork 4
/
cppguide_ru.html
5304 lines (4294 loc) · 377 KB
/
cppguide_ru.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
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
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
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
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Руководство Google по стилю в C++</title>
<link rel="stylesheet" href="include/styleguide_ru.css" />
<script src="include/styleguide_ru.js"></script>
<link rel="shortcut icon" href="https://www.google.com/favicon.ico" />
</head>
<body onload="initStyleGuide();">
<div id="content">
<h1>Руководство Google по стилю в C++</h1>
<div class="horizontal_toc" id="tocDiv"></div>
<h2 id="Background" class="ignoreLink">Вступление</h2>
<p>C++ один из основных языков программирования, используемый в open-source проектах Google.
Известно, что C++ очень мощный язык. Вместе с тем это сложный язык и, при неправильном использовании,
может быть рассадником багов, затруднить чтение и поддержку кода.</p>
<p>Цель руководства - управлять сложностью кода,
описывая в деталях как стоит, и как не стоит, писать код на C++.
Правила этого руководства упростят управление кодом и увеличат продуктивность кодеров.</p>
<p><em>Style / Стиль</em> - соглашения, которым следует C++ код.
Стиль - это больше, чем форматирование файла с кодом.</p>
<p>Большинство open-source проектов, разрабатываемых Google, соответствуют этому руководству.</p>
<p>Примечание: это руководство не является учебником по C++:
предполагается, что вы знакомы с языком.</p>
<h3 id="Goals">Цели Руководства по стилю</h3>
<p>Зачем нужен этот документ?</p>
<p>Есть несколько основных целей этого документа, внутренних
<b>Зачем</b>, лежащих в основе отдельных правил.
Используя эти цели можно избежать длинных дискуссий: почему правила такие и зачем им следовать.
Если вы понимаете цели каждого правила, то вам легче с ними согласиться или отвергнуть,
оценить альтернативы при изменении правил под себя.</p>
<p>Цели руководства следующие::</p>
<dl>
<dt>Правила должны стоить изменений</dt>
<dd>Преимущества от использования единого стиля должны перевешивать
недовольство инженеров по запоминанию и использованию правил.
Преимущество оценивается по сравнению с кодовой базой без применения правил,
поэтому если ваши люди всё равно не будут применять правила,
то выгода будет очень небольшой.
Этот принцип объясняет почему некоторые правила отсутствуют:
например, <code>goto</code> нарушает многие принципы, однако он практически не используется,
поэтому Руководство это не описывает.</dd>
<dt>Оптимизировано для чтения, не для написания</dt>
<dd>Наша кодовая база (и большинство отдельных компонентов из неё)
будет использоваться продолжительное время. Поэтому, на чтение этого кода
будет тратиться существенно больше времени, чем на написание.
Мы явно заботимся чтобы нашим инженерам было легко читать, поддерживать,
отлаживать код. "Оставляй отладочный/логирующий код" - одно из следствий:
когда кусок кода работает "странно" (например, при передаче владения указателем),
наличие текстовых подсказок может быть очень полезным (<code>std::unique_ptr</code>
явно показывает передачу владения). </dd>
<dt>Пиши код, похожий на существующий</dt>
<dd>Использование единого стиля на кодовой базе позволяет переключиться на другие, более важные, вопросы.
Также, единый стиль способствует автоматизации. И, конечно, автоформат кода
(или выравнивание <code>#include</code>-ов) работает правильно, если он
соответствует требованиям утилиты. В нестандартных случаях можно порекомендовать
выбрать одно (наиболее подходящее) правило и ему следовать (и меньше спорить по
этому поводу). С другой стороны, следование
единому стилю тоже должно быть разумным. Использование текущего (единого) стиля
хорошо работает для изолированных интерфейсов или отдельных файлов, когда нет проблем
технического характера или смены парадигмы. Однако, следование единому стилю не
должно препятствовать внедрению нового стиля, если в нём есть явные преимущества или
код переводится в новый стиль.</dd>
<dt>Пиши код, похожий на используемый в C++ сообщества (по возможности)</dt>
<dd>Согласованность нашего кода с C++ кодом других организаций и сообществ
весьма полезна. Если возможности стандартного C++ или принятые идиомы языка
облегчают написание программ, это повод использовать их. Однако,
иногда стандарт и идиомы плохо подходят для задачи. В этих случаях
(как описано ниже) имеет смысл ограничить или запретить использование
некоторых стандартных возможностей. В некоторых случаях создаётся свой решение,
но иногда используются внешние библиотеки (вместо стандартной библиотеки C++)
и переписывание её под свой стандарт слишком затратно.</dd>
<dt>Избегайте неожиданных или опасных конструкций</dt>
<dd>В языке C++ есть неочевидные и даже опасные подходы. Некоторые
стили кодирования ограничивают их использование, т.к. их использование
несёт большие риски для правильности кода.</dd>
<dt>Избегайте конструкций, которые средний C++ программист считает заумными
и сложно поддерживаемыми</dt>
<dd>В C++ есть возможности, которые в целом не приветствуются по причине
усложнения кода. Однако, в часто используемом коде применение хитрых
конструкций более оправданно благодаря многократному использованию,
также новые порции кода станут более понятны.
В случае сомнений - проконсультируйтесь с руководством проекта.
Это очень важно для нашей кодовой базы, т.к. владельцы кода и команда поддержки
меняются со временем: даже если сейчас все понимают код,
через несколько лет всё может измениться.</dd>
<dt>Учитывайте масштаб кода</dt>
<dd>С кодовой базой более 100 миллионов строк и тысячами инженеров,
ошибки и упрощения могут дорого обойтись. Например, важно избегать
замусоривания глобального пространства имён: коллизии имён очень
сложно избежать в большой базе кода если всё объявляется в глобальном
пространстве имён.</dd>
<dt>Оптимизируйте по необходимости</dt>
<dd>Оптимизация производительности иногда важнее, чем следование правилам в кодировании.</dd>
</dl>
<p>Намерение этого документа - обеспечить максимально понятное руководство
при разумных ограничениях. Как всегда, здравый смысл никто не отменял.
Этой спецификацией мы хотим установить соглашения для всего сообщества Google в C++,
не только для отдельных команд или людей. Относитесь со скепсисом к хитрым или
необычным конструкциям: отсутствие ограничения не всегда есть разрешение.
И, если не можешь решить сам, спроси начальника.</p>
<h2 id="C++_Version">Версия C++</h2>
<p>Сейчас код должен соответствовать C++20.
Возможности языка, относящиеся к C++23, использовать не следует.
В дальнейшем руководство будет корректироваться на более новые версии C++.</p>
<p>Не используйте
<a href="#Nonstandard_Extensions">нестандартные расширения</a>.</p>
<p>Прежде чем использовать возможности C++17 и C++20 в проектах, оцените возможность
портирования кода для другого окружения.</p>
<h2 id="Header_Files">Заголовочные файлы</h2>
<p>Желательно, чтобы каждый <code>.cc</code> файл исходного кода
имел парный <code>.h</code> заголовочный файл. Также есть известные
исключения из этого правила, такие как юниттесты или небольшие
<code>.cc</code> файлы, содержащие только функцию <code>main()</code>.</p>
<p>Правильное использование заголовочных файлов может оказать
огромное влияние на читабельность, размер и производительность вашего кода.</p>
<p>Следующие правила позволят избежать частых проблем с заголовочными файлами.</p>
<h3 id="Self_contained_Headers">Независимые заголовочные файлы</h3>
<p>Заголовочные файлы должны быть самодостаточными (в плане компиляции)
и иметь расширение <code>.h</code>. Другие файлы (не заголовочные), предназначенные
для включения в код, должны быть с расширением <code>.inc</code> и использоваться
в паре с включающим кодом.</p>
<p>Все заголовочные файлы должны быть самодостаточными. Пользователи и инструменты разработки не
должны зависеть от специальных зависимостей при использовании заголовочного файла.
Заголовочный файл должен иметь <a href="#The__define_Guard">блокировку от повторного включения</a> и
включать все необходимые файлы.</p>
<p>Когда в заголовочном файле объявляются встраиваемые функции или шаблоны (которые
будут инстанцироваться внешним кодом), они должны целиком определяться в
заголовочных файлах: либо в том же самом, либо во включаемых файлах.
Не выделяйте определения в отдельные заголовочные файлы
(<code>-inl.h</code>). Раньше такая практика была очень популярна, сейчас
это нежелательно. В случае, если все инстанцирования шаблона производятся в
одном .cc файле (либо они явные, либо шаблон используется только в этом файле),
то определение шаблона может храниться в этом .cc файле.</p>
<p>Возможны редкие ситуации, когда заголовочный файл не самодостаточный.
Это может происходить, когда файл подключается в нестандартном месте,
например в середине другого файла. В этом случае может отсутствовать
<a href="#The__define_Guard">блокировка от повторного включения</a>, и
дополнительные заголовочные файлы также могут не подключаться.
Именуйте такие файлы расширением <code>.inc</code>. Используйте их парой и
старайтесь чтобы они максимально соответствовали общим требованиям.</p>
<h3 id="The__define_Guard">Блокировка от повторного включения</h3>
<p>Все заголовочные файлы должны быть с защитой от повторного включения посредством
<code>#define</code>. Формат макроопределения должен быть:
<code><i><PROJECT></i>_<i><PATH></i>_<i><FILE></i>_H_</code>.</p>
<div>
<p>Для гарантии уникальности, используйте компоненты полного пути к файлу в дереве проекта.
Например, файл <code>foo/src/bar/baz.h</code> в проекте <code>foo</code> может иметь следующую блокировку:</p>
</div>
<pre>#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
</pre>
<h3 id="Include_What_You_Use">Подключайте используемые заголовочные файлы</h3>
<p>Если файл с кодом или заголовочный используют внешние зависимости/объявления,
тогда в этот файл необходимо напрямую подключать файл с этими объявлениями. В иных
случаях не используйте подключение внешнего файла.</p>
<p>Не полагайтесь на вложенные подключения файлов. Это позволит удалить уже не
используемые <code>#include</code> сохранив корректность другого кода. Правило используется
даже в случае парных файлов: даже если <code>foo.h</code> подключает <code>bar.h</code>, то
<code>foo.cc</code> также подключает <code>bar.h</code> если использует
определения из последнего.</p>
<h3 id="Forward_Declarations">Предварительное объявление</h3>
<p>По возможности, не используйте предварительное объявление.
Вместо этого <a href="#Include_What_You_Use">подключайте используемые заголовочные файлы</a>.</p>
<p class="definition"></p>
<p>"Предварительное объявление" - декларация сущности без
соответствующего определения.</p>
<pre>// В C++ файле:
class B;
void FuncInB();
extern int variable_in_b;
ABSL_DECLARE_FLAG(flag_in_b);
</pre>
<p class="pros"></p>
<ul>
<li>Предварительное объявление может уменьшить время компиляции.
Использование <code>#include</code> потребует от компилятора сразу
открывать (и обрабатывать) больше файлов.</li>
<li>Предварительное объявление позволит избежать ненужной
перекомпиляции. Применение <code>#include</code> может привести к
частой перекомпиляции из-за различных изменений в заголовочных файлах.</li>
</ul>
<p class="cons"></p>
<ul>
<li>Предварительное объявление может скрывать от перекомпиляции
зависимости, которые изменились.</li>
<li>Предварительное объявление (вместо <code>#include</code>) затрудняет поиск модуля с
необходимым определением для утилит диагностики кода.</li>
<li>При изменении API, предварительное объявление может стать некорректным.
Как результат, предварительное объявление функций или шаблонов может блокировать
изменение API: замена типов параметров на похожий, добавление параметров
по умолчанию в шаблон, перенос в новое пространство имён.</li>
<li>Предварительное объявление символов из <code>std::</code> может вызвать
неопределённое поведение.</li>
<li>Иногда тяжело понять, что лучше подходит: предварительное объявление или
обычный <code>#include</code>.
Однако, замена <code>#include</code> на предварительное объявление может (без предупреждений) поменять
смысл кода:
<pre>// b.h:
struct B {};
struct D : B {};
// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); } // calls f(B*)
</pre>
Если в коде заменить <code>#include</code> на предварительное объявление
для структур <code>B</code> и <code>D</code>, то
<code>test()</code> будет вызывать <code>f(void*)</code>.
</li>
<li>Предварительное объявление множества сущностей может быть
чересчур объёмным, и может быть проще подключить заголовочный файл.</li>
<li>Структурирование кода, допускающего предварительное объявление
(и использующее указатели как члены класса вместо самих объектов)
может сделать код запутанным и медленным.</li>
</ul>
<p class="decision"></p>
<p>Старайтесь избегать предварительного объявления сущностей,
объявленных в другом проекте.</p>
<h3 id="Inline_Functions">Встраиваемые (inline) функции</h3>
<p>Определяйте функции как встраиваемые только когда они маленькие,
например не более 10 строк.</p>
<p class="definition"></p>
<p>Вы можете объявлять функции встраиваемыми и указать компилятору на возможность
включать её напрямую в вызывающий код, помимо стандартного способа с вызовом функции.</p>
<p class="pros"></p>
<p>Использование встраиваемых функций может генерировать более эффективный код,
особенно когда функции маленькие. Используйте эту возможность для get/set функций,
других коротких и критичных для производительности функций.</p>
<p class="cons"></p>
<p>Чрезмерное использование встраиваемых функций может сделать программу медленнее.
Также встраиваемые функции, в зависимости от размера её, могут как увеличить, так и уменьшить
размер кода. Если это маленькие функции, то код может быть уменьшен.
Если же функция большая, то размер кода может очень сильно вырасти.
Учтите, что на современных процессорах более компактный код выполняется быстрее
благодаря лучшему использованию кэша инструкций.</p>
<p class="decision"></p>
<p>Хорошим правилом будет не делать функции встраиваемыми, если они превышают
10 строк кода. Избегайте делать встраиваемыми деструкторы, т.к. они неявно
могут содержать много дополнительного кода: вызовы деструкторов переменных и
базовых классов!</p>
<p>Ещё одно хорошее правило: обычно нет смысла делать встраиваемыми функции,
в которых есть циклы или операции switch (кроме вырожденных случаев, когда цикл
или другие операторы никогда не выполняются).</p>
<p>Важно понимать, что встраиваемая функция необязательно будет скомпилирована
в код именно так. Например, обычно виртуальные и рекурсивные функции компилируются
со стандартным вызовом. Вообще, рекурсивные функции не должны объявляться встраиваемыми.
Основная же причина делать встраиваемые виртуальные функции - разместить определение (код)
в самом определении класса (для документирования поведения или удобства чтения) - часто
используется для get/set функций.</p>
<h3 id="Names_and_Order_of_Includes">Имена и Порядок включения (include)</h3>
<p>Вставляйте заголовочные файлы в следующем порядке:
парный файл (например, foo.h - foo.cc), системные файлы C, стандартная библиотека C++,
другие библиотеки, файлы вашего проекта.</p>
<p>
Все заголовочные файлы проекта должны указываться относительно
директории исходных файлов проекта без использования таких UNIX псевдонимов
как <code>.</code> (текущая директория) или <code>..</code>
(родительская директория). Например,
<code>google-awesome-project/src/base/logging.h</code>
должен включаться так:</p>
<pre>#include "base/logging.h"
</pre>
<p>При подключении заголовочных файлов используйте угловые скобки только если
это требуется библиотекой. В частности, следующие заголовочные файлы потребуют
использования угловых скобок:</p>
<ul>
<li>Заголовочные файлы стандартной библиотеки C и C++ (например, <code><stdlib.h></code>
и <code><string></code>).</li>
<li>Системные заголовочные файлы POSIX, Linux и Windows (например, <code><unistd.h></code>
и <code><windows.h></code>).</li>
<li>В редких случаях это требуется и для других библиотек (например, <code><Python.h></code>).</li>
</ul>
<p>Пример: если основная функция файлов <code><var>dir/foo</var>.cc</code> и
<code><var>dir/foo_test</var>.cc</code>
это реализация и тестирование кода, объявленного в
<code><var>dir2/foo2</var>.h</code>, то записывайте заголовочные файлы
в следующем порядке:</p>
<ol>
<li><code><var>dir2/foo2</var>.h</code>.</li>
<li>------ Пустая строка</li>
<li>Системные заголовочные файлы C и любые другие в угловых скобках
с расширением <code>.h</code>, например <code><unistd.h></code>,
<code><stdlib.h></code>, <code><Python.h></code>.</li>
<li>------ Пустая строка</li>
<li>Заголовочные файлы стандартной библиотеки C++ (без расширения в файлах), например
<code><algorithm></code>, <code><cstddef></code>.</li>
<li>------ Пустая строка</li>
<li>Заголовочные <code>.h</code> файлы других библиотек.</li>
<li>------ Пустая строка</li>
<li>Файлы <code>.h</code> вашего проекта.</li>
</ol>
<p>Отделяйте каждую (непустую) группу файлов пустой строкой.</p>
<p>Такой порядок файлов позволяет выявить ошибки, когда
в парном заголовочном файле (<code><var>dir2/foo2</var>.h</code>)
пропущены необходимые заголовочные файлы (системные и др.) и
сборка соответствующих файлов <code><var>dir/foo</var>.cc</code>
или <code><var>dir/foo</var>_test.cc</code> завершится ошибкой.
Как результат, ошибка сразу же появится у разработчика,
работающего с этими файлами (а не у другой команды,
которая только использует внешнюю библиотеку).</p>
<p>Обычно парные файлы <code><var>dir/foo</var>.cc</code> и
<code><var>dir2/foo2</var>.h</code> находятся в одной директории
(например, <code>base/basictypes_test.cc</code> и
<code>base/basictypes.h</code>), хотя это не обязательно.</p>
<p>Учтите, что заголовочные файлы C, такие, как <code>stddef.h</code>,
обычно взаимозаменяемы соответствующими файлами C++ (<code>cstddef</code>).
Можно использовать любой вариант, но лучше следовать стилю
существующего кода.</p>
<p>Внутри каждой секции заголовочные файлы лучше всего перечислять в алфавитном порядке.
Учтите, что ранее написанный код может не следовать этому правилу. По возможности
(например, при исправлениях в файле), исправляйте порядок файлов на правильный.</p>
<p>Например, список заголовочных файлов в
<code>google-awesome-project/src/foo/internal/fooserver.cc</code>
может выглядеть так:</p>
<pre>#include "foo/server/fooserver.h"
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "foo/server/bar.h"
#include "third_party/absl/flags/flag.h"
</pre>
<p><b>Исключения:</b></p>
<p>Бывают случаи, когда требуется включение заголовочных файлов в зависимости
от условий препроцессора (например, в зависимости от используемой ОС).
Такое включение старайтесь делать как можно короче (локализовано) и размещать после
других заголовочных файлов. Например:</p>
<pre>#include "foo/public/fooserver.h"
#include "base/port.h" // For LANG_CXX11.
#ifdef LANG_CXX11
#include <initializer_list>
#endif // LANG_CXX11
</pre>
<h2 id="Scoping">Область видимости</h2>
<h3 id="Namespaces">Пространство имён</h3>
<p>Размещайте свой код в пространстве имён (за некоторыми исключениями). Пространство имён должно
иметь уникальное имя, формируемое на основе названия проекта, и, возможно, пути.
Не используйте директиву <i>using</i> (например, <code>using namespace foo</code>). Не используйте
встроенные (inline) пространства имён. Для безымянных пространств имён смотрите
<a href="#Internal_Linkage">Внутреннее связывание</a>.</p>
<p class="definition"></p>
<p>Пространства имён делят глобальную область видимости на отдельные именованные
области, позволяя
избежать совпадения (коллизий) имён.</p>
<p class="pros"></p>
<p>Пространства имён позволяют избежать конфликта имён в больших программах, при этом сами имена
остаются достаточно короткими.</p>
<p>Например, если два разных проекта содержат класс <code>Foo</code> в глобальной области видимости,
имена могут конфликтовать. Если каждый проект размещает код в своё пространство имён, то
<code>project1::Foo</code> и <code>project2::Foo</code> будут разными именами, конфликтов не будет,
в то же время код каждого проекта будет использовать <code>Foo</code> без префикса.</p>
<p>Пространства имён inline автоматически делают видимыми свои имена для включающего
пространства имён. Рассмотрим пример кода:</p>
<pre class="neutralcode">namespace outer {
inline namespace inner {
void foo();
} // namespace inner
} // namespace outer
</pre>
<p>Здесь выражения <code>outer::inner::foo()</code> и
<code>outer::foo()</code> взаимозаменяемы. Inline пространства имён
в основном используются для ABI-совместимости разных версий.</p>
<p class="cons"></p>
<p>Пространства имён могут запутать программиста, усложнить понимание, что к чему относится.</p>
<p>Пространства имён inline, в частности, могут сбивать с толку
т.к. область видимости не ограничена местом определения. Поэтому такой вид
пространств имён может быть полезен только при обновлении интерфейсов с сохранением совместимости.</p>
<p>В ряде случаев требуется использование полных имён и это может сделать код сильно перегруженным.</p>
<p class="decision"></p>
<p>Используйте пространства имён следующим образом:</p>
<ul>
<li>Следуйте правилам <a href="#Namespace_Names">Именования Пространств Имён</a>.
</li><li>В конце объявления многострочного пространства имён добавляйте комментарий, аналогично
показанным в примерах.</li><li>
<p>Заключайте в пространство имён целиком файл с исходным кодом после #include-ов,
объявлений/определений <a href="https://gflags.github.io/gflags/">
gflag-ов</a> и предварительных объявлений классов из других пространств имён.</p>
<pre>// В .h файле
namespace mynamespace {
// Все объявления внутри блока пространства имён.
// Обратите внимание на отсутствие отступа.
class MyClass {
public:
...
void Foo();
};
} // namespace mynamespace
</pre>
<pre>// В .cc файле
namespace mynamespace {
// Определение функций внутри блока пространства имён.
void MyClass::Foo() {
...
}
} // namespace mynamespace
</pre>
<p>В <code>.cc</code> файлах могут быть дополнительные объявления, такие как флаги или using-декларации.</p>
<pre>#include "a.h"
ABSL_FLAG(bool, someflag, false, "a flag");
namespace mynamespace {
using ::foo::Bar;
...code for mynamespace... // Код начинается с самой левой границы.
} // namespace mynamespace
</pre>
</li>
<li>Чтобы генерируемый из protobuf-а код был размещён в требуемом пространстве имён,
применяйте спецификатор <code>package</code> в <code>.proto</code> файле. Подробнее здесь:
<a href="https://developers.google.com/protocol-buffers/docs/reference/cpp-generated#package">
Protocol Buffer Packages</a>.</li>
<li>Ничего не объявляйте в пространстве <code>std</code>, в том числе и
предварительные объявления классов стандартной библиотеки. Объявление в
пространстве имён <code>std</code> приведёт к неопределённому поведению (UB)
и это будет непереносимый код. Для объявления сущностей используйте соответствующий
заголовочный файл.</li>
<li><p>Не используйте <i>using-директиву</i>, чтобы сделать доступными все
имена из пространства имён.</p>
<pre class="badcode">// Недопустимо -- Это загрязняет пространство имён.
using namespace foo;
</pre>
</li>
<li><p>Не используйте <i>псевдонимы пространств имён</i> в блоке namespace в заголовочном файле,
за исключением явно обозначенных "внутренних" пространств имён. Связано это с тем, что любая
декларация в заголовочном файле становится частью публичного API, экспортируемого этим файлом.</p>
<pre>// Укороченная запись для доступа к часто используемым именам в .cc файлах.
namespace baz = ::foo::bar::baz;
</pre>
<pre>// Укороченная запись для доступа к часто используемым именам (в .h файле).
namespace librarian {
namespace internal { // Внутреннее содержимое, не являющееся частью API.
namespace sidetable = ::pipeline_diagnostics::sidetable;
} // namespace internal
inline void my_inline_function() {
// Пространство имён, локальное для функции (или метода).
namespace baz = ::foo::bar::baz;
...
}
} // namespace librarian
</pre>
</li><li>Не используйте inline-пространства имён.</li>
<li><p>Если пространство имён не предназначено для пользователей API, то используйте
слово "internal" в его имени.
</p>
<pre class="badcode">// Не следует использовать такие внутренние имена в не-absl коде.
using ::absl::container_internal::ImplementationDetail;
</pre>
</li>
<li><p>В новом коде предпочтительно декларировать пространство имён в одну строчку.
Это не является обязательным требованием.</p>
</li>
</ul>
<a id="Unnamed_Namespaces_and_Static_Variables"></a>
<h3 id="Internal_Linkage">Внутреннее связывание</h3>
<p>Когда определения внутри <code>.cc</code> файла не используются в других
исходных файлах, используйте для них внутреннее связывание: размещайте
такие определения в безымянном пространстве имён или объявляйте их как
<code>static</code>. Не используйте такие конструкции в <code>.h</code> файлах.
</p><p class="definition"></p>
<p>Размещённые в безымянном пространстве имён объявления сразу получают внутреннее связывание.
Функции и переменные также могут быть с внутренним связыванием, если они заявлены как <code>static</code>.
Такие типы объявления подразумевают, что они будут недоступны из другого файла. Если другой файл объявляет
сущность с таким же именем, то оба объявления будут полностью независимы.</p>
<p class="decision"></p>
<p>Использование внутреннего связывания в <code>.cc</code> файлах предпочтительно для любого кода,
к которому не обращаются снаружи (из других файлов). Не используйте внутреннее
связывание в <code>.h</code> файлах.</p>
<p>Формат описания безымянного пространства имён полностью аналогичен именованному варианту. Не забывайте к закрывающей скобке написать комментарий, в котором
имя оставьте пустым:</p>
<pre>namespace {
...
} // namespace
</pre>
<h3 id="Nonmember,_Static_Member,_and_Global_Functions">Функции: глобальные, статические, вне класса</h3>
<p>Предпочтительно заключать отдельные функции (вне класса) в пространство имён.
Использование полностью глобальных функций должно быть минимальным. Также не
используйте класс только лишь для группировки функций, объявляя
их статическими. Статические методы класса должны использоваться при совместной работе
с экземплярами класса или его статическими данными.</p>
<p class="pros"></p>
<p>Статические функции-члены или функции вне класса могут быть полезными в отдельных ситуациях. И размещение функций - не членов в пространстве имён позволяет содержать в чистоте глобальное пространство имён.</p>
<p class="cons"></p>
<p>Иногда разумнее статические функции класса и функции вне класса сгруппировать в одном месте, в новом классе.
Например, когда у них сложные зависимости от всего или им нужен доступ к внешним ресурсам.</p>
<p class="decision"></p>
<p>Иногда полезно объявить функцию, не привязанную к экземпляру класса. И можно сделать либо
статическую функцию в классе, либо внешнюю (вне класса) функцию. Желательно, чтобы функция-вне-класса
не использовала внешних переменных и находилась в пространстве имён. Не создавайте классы только для
группировки статических членов: это всё равно, что дать именам некий префикс и группировка становится
лишней.</p>
<p>Если определяется функция вне класса и она используется только в этом
<code>.cc</code>-файле, то используйте <a href="#Internal_Linkage">внутреннее связывание</a> для ограничения области видимости.</p>
<h3 id="Local_Variables">Локальные переменные</h3>
<p>Объявляйте переменные внутри функции в наиболее узкой
области видимости, инициализируйте такие переменные при объявлении.</p>
<p>Язык C++ позволяет объявлять переменные в любом месте функции. Однако
рекомендуется делать это в наиболее вложенной области
видимости и по возможности ближе к первому использованию переменной. Это облегчает поиск
объявлений, проще узнать тип переменной и её начальное значение. Также
рекомендуется использовать инициализацию, а не объявление с присваиванием.
Примеры:</p>
<pre class="badcode">int i;
i = f(); // Плохо -- инициализация отделена от объявления.
</pre>
<pre>int j = f(); // Хорошо -- объявление с инициализацией.
</pre>
<pre class="badcode">int jobs = NumJobs();
// ... другой код ...
f(jobs); // Плохо -- декларация отделена от использования.
</pre>
<pre>int jobs = NumJobs();
f(jobs); // Хорошо -- декларация прямо перед использованием (или близко к нему).
</pre>
<pre class="badcode">std::vector<int> v;
v.push_back(1); // Желательно инициализировать с помощью {}.
v.push_back(2);
</pre>
<pre>std::vector<int> v = {1, 2}; // Хорошо -- v сразу инициализирован.
</pre>
<p>Переменные, необходимые только внутри кода <code>if</code>, <code>while</code>
и <code>for</code> лучше объявлять внутри условий, тогда область
их видимости будет ограничена только соответствующим блоком кода:</p>
<pre>while (const char* p = strchr(str, '/')) str = p + 1;
</pre>
<p>Однако учитывайте одну тонкость: если переменная есть экземпляр объекта,
то при каждом входе в область видимости будет вызываться конструктор,
и, соответственно, при выходе будет вызываться деструктор.</p>
<pre class="badcode">// Неэффективная реализация:
for (int i = 0; i < 1000000; ++i) {
Foo f; // Конструктор и деструктор Foo вызовутся по 1000000 раз каждый.
f.DoSomething(i);
}
</pre>
<p>Возможно было бы более эффективно такую переменную (которая
используется внутри цикла) объявить вне цикла:</p>
<pre>Foo f; // Конструктор и деструктор Foo вызовутся по разу.
for (int i = 0; i < 1000000; ++i) {
f.DoSomething(i);
}
</pre>
<h3 id="Static_and_Global_Variables">Переменные: статические и глобальные</h3>
<p>Объекты в
<a href="http://en.cppreference.com/w/cpp/language/storage_duration#Storage_duration">
статической области</a> видимости/действия запрещены, кроме
<a href="http://en.cppreference.com/w/cpp/types/is_destructible">тривиально удаляемых</a>.
Фактически это означает, что деструктор должен ничего не делать (включая вложенные или
базовые типы). Формально это можно описать, что тип не содержит пользовательского
или виртуального деструктора и что все базовые типы и не-статические члены ведут себя
аналогично (т.е. являются тривиально удаляемыми). Статические переменные в функциях
могут быть динамически инициализированными. Использование же динамической инициализации
для статических членов класса или переменных в области пространства имён
(namespace) в целом не рекомендуется, однако допустимо в ряде случаев (см. ниже).</p>
<p>Эмпирическое правило: если глобальную переменную (рассматривая её изолированно) можно
объявить как <code>constexpr</code>, значить она соответствует вышеуказанным требованиям.</p>
<p class="definition"></p>
<p>Каждый объект имеет тот или иной тип <dfn>времени жизни / storage duration</dfn>,
и, очевидно, это влияет на время жизни объекта. Объекты статического типа
доступны с момента их инициализации до момента завершения программы. Такие объекты
могут быть переменными в пространстве имён ("глобальные переменные"),
статическими членами классов, локальными переменными внутри функций со
спецификатором <code>static</code>. Статические переменные в функциях
инициализируются, когда поток выполнения кода проходит в первый раз через объявление;
все остальные объекты статического типа инициализируются в фазе старта (start-up)
приложения. Все объекты статического типа удаляются в фазе завершения
программы (до обработки незавершённых(unjoined) потоков).</p>
<p>Инициализация может быть <dfn>динамическая</dfn>, т.е. во время инициализации
делается что-то нетривиальное: например, конструктор выделяет память, или
переменная инициализируется идентификатором процесса. Также инициализации может быть
<dfn>статической</dfn>. Сначала выполняется статическая инициализация: для <em>всех</em>
объектов статического типа (объект инициализируется либо заданной константой,
либо заполняется нулями). Далее, если необходимо, выполняется динамическая инициализация.</p>
<p class="pros"></p>
<p>Глобальные и статические переменные бывают очень полезными: константные имена,
дополнительные структуры данных, флаги командной строки, логирование,
регистрирование, инфраструктура и др.</p>
<p class="cons"></p>
<p>Глобальные и статические переменные с динамической инициализацией или
нетривиальным деструктором могут сильно усложнить код и привести к трудно обнаруживаемым
багам. Порядок динамической инициализации (и разрушения) объектов может быть различным
когда есть несколько единиц трансляции. И, например, когда одна из инициализаций
ссылается на некую переменную статического типа, то возможна ситуация доступа
к объекту до корректного начала его жизненного цикла (до полного конструирования),
или уже после окончания жизненного цикла. Если программа создаёт несколько
потоков, которые не завершаются к моменту выхода из программы, то они
могут попытаться получить доступ к объектам, которые уже разрушены.</p>
<p class="decision"></p>
<p>Когда деструктор тривиальный, тогда порядок разрушения в принципе не важен. В
противном случае есть риск обратиться к объекту после его разрушения.
Поэтому, настоятельно рекомендуется использовать только переменные со
статическим типом размещения (конечно, если они имеют тривиальный деструктор).
Фундаментальные типы (указатели или <code>int</code>), как и массивы из них,
являются тривиально разрушаемыми. Переменные с типом constexp также
тривиально разрушаемые.</p>
<pre>const int kNum = 10; // Допустимо
struct X { int n; };
const X kX[] = {{1}, {2}, {3}}; // Допустимо
void foo() {
static const char* const kMessages[] = {"hello", "world"}; // Допустимо
}
// Допустимо: constexpr гарантирует, что деструктор будет тривиальный
constexpr std::array<int, 3> kArray = {1, 2, 3};</pre>
<pre class="badcode">// Плохо: нетривиальный деструктор
const std::string kFoo = "foo";
// Плохо по тем же причинам (хотя kBar и является ссылкой, но
// правило применяется и для временных объектов в расширенным временем жизни)
const std::string& kBar = StrCat("a", "b", "c");
void bar() {
// Плохо: нетривиальный деструктор
static std::map<int, int> kData = {{1, 0}, {2, 0}, {3, 0}};
}</pre>
<p>Отметим, что ссылка не есть сам объект, и, следовательно, к ним не применяются
ограничения по разрушению объекта. Хотя ограничения на динамическую
инициализацию остаются в силе. В частности, внутри функции допустим следующий
код <code>static T& t = *new T;</code>.</p>
<h4>Тонкости инициализации</h4>
<p>Инициализация может быть запутанной: мало того, что конструктору нужно
(желательно правильно) отработать, так есть ещё и предварительные вычисления:</p>
<pre class="neutralcode">int n = 5; // Отлично
int m = f(); // ? (Зависит от f)
Foo x; // ? (Зависит от Foo::Foo)
Bar y = g(); // ? (Зависит от g и Bar::Bar)
</pre>
<p>На выполнение всех выражений, кроме первого, может повлиять порядок инициализации,
который может быть разным/неопределённым (или зависимым от ...).</p>
<p>Рассмотрим идею <em>константной инициализации</em>, как она обозначается в стандарте C++. Это означает, что инициализационное
выражение - константное, и если при создании объекта вызывается конструктор, то
он (конструктор) тоже должен быть заявлен как <code>constexpr</code>:</p>
<pre class="goodcode">struct Foo { constexpr Foo(int) {} };
int n = 5; // Отлично, 5 - константное выражение
Foo x(2); // Отлично, 2 - константное выражение и вызывается constexpr конструктор
Foo a[] = { Foo(1), Foo(2), Foo(3) }; // Отлично</pre>
<p>Константная инициализация является рекомендуемой для большинства случаев.
Константную инициализацию переменных со статическим размещением рекомендуется
помечать как <code>constexpr</code> или <code>constinit</code>.
Любую не-локальную переменную со статическим
размещением и без указанной выше маркировки следует считать динамически
инициализируемой (и тщательно проверять на ревью кода).</p>
<p>Например, следующие инициализации могут привести к проблемам:</p>
<pre class="badcode">// Объявления
time_t time(time_t*); // Не constexpr !
int f(); // Не constexpr !
struct Bar { Bar() {} };
// Проблемные инициализации
time_t m = time(nullptr); // Инициализационное выражение не константное
Foo y(f()); // Те же проблемы
Bar b; // Конструктор Bar::Bar() не является constexpr</pre>
<p>Динамическая инициализация переменных вне функций не рекомендуется. В общем
случае это запрещено, однако, это можно делать если никакой код программы не
зависит от порядка инициализации этой переменной среди других: в этом случае
изменение порядка инициализации не может что-то поломать. Например:</p>
<pre>int p = getpid(); // Допустимо, пока другие статические переменные
// не используют p в своей инициализации</pre>
<p>Динамическая же инициализация статических переменных в функциях (локальных)
допустима и является широко распространённой практикой.</p>
<h4>Стандартные практики</h4>
<ul>
<li>Глобальные строки: если требуется именованная глобальная или статическая
строковая константа, то рекомендуется использовать переменную
<code>constexpr string_view</code>, символьный
массив или указатель на первый символ строкового литерала. Строковые
литералы обычно находятся в статическом (по времени жизни) размещении и
этого в большинстве случаев достаточно. Смотрите также:
<a href="https://abseil.io/tips/140">TotW #140.</a></li>
<li>Динамические контейнеры (map, set и т.д.): если требуется статическая
коллекция с фиксированными данными (например, таблицы значений для поиска),
то не используйте динамические контейнеры из стандартной библиотеки как
тип для статической переменной, т.к. у этих контейнеров нетривиальный
деструктор. Вместо этого попробуйте использовать массивы простых (тривиальных)
типов, например массив из массивов целых чисел (вместо
<code>std::map<int, int></code>) или,
например, массив структур с полями <code>int</code> и <code>const char*</code>.
Учтите, что для небольших коллекций линейный поиск обычно вполне приемлем
(и может быть очень эффективным благодаря компактному размещению в памяти).
Также можете воспользоваться алгоритмами
<a href="https://github.com/abseil/abseil-cpp/blob/master/absl/algorithm/container.h">absl/algorithm/container.h</a>
для стандартных операций. Также возможно создавать коллекцию данных уже
отсортированной и использовать алгоритм бинарного поиска. Если без
динамического контейнера не обойтись, то попробуйте использовать
статическую переменную-указатель, объявленную в функции (см. ниже).</li>
<li>Умные указатели (<code>std::unique_ptr</code>, <code>std::shared_ptr</code>): умные указатели
освобождают ресурсы в деструкторе и поэтому использовать их нельзя. Попробуйте
применить другие практики/способы, описанные в разделе. Например, одно из
простых решений это использовать обычный указатель на динамически выделенный
объект и далее никогда не удалять его (см. последний вариант списка).</li>
<li>Статические переменные пользовательского типа: если требуется статический и
константный пользовательский тип, заполненный данными, то можете объявить у
этого типа тривиальный деструктор и <code>constexpr</code> конструктор.</li>
<li>Если все другие способы не подходят, то можно создать динамический объект и
никогда не удалять его. Объект можно создать с использованием статического указателя
или ссылки, объявленной в функции, например: <code>static
const auto& impl = *new T(args...);</code>.</li>
</ul>
<h3 id="thread_local">Потоковые переменные (thread_local)</h3>
<p>Потоковые переменные (<code>thread_local</code>), объявленные вне функций
должны быть инициализированы константой, вычисляемой во время компиляции. И это
должно быть сделано с помощью атрибута
<a href="https://en.cppreference.com/w/cpp/language/constinit"><code>constinit</code></a>.
В целом, для определения данных, специфичных для каждого потока,
использование <code>thread_local</code> является наиболее предпочтительным.</p>
<p class="definition"></p>
<p>Переменные можно объявлять со спецификатором <code>thread_local</code>:</p>
<pre>thread_local Foo foo = ...;
</pre>
<p>Каждая такая переменная представляется собой коллекцию объектов. Разные потоки работают
с разными экземплярами переменной (каждый со своим экземпляром). По поведению
переменные <code>thread_local</code> во многом похожи на
<a href="#Static_and_Global_Variables">Переменные со статическим типом размещения</a>.
Например, они могут быть объявлены в пространстве имён, внутри функций,
как статические члены класса (как обычные члены класса - нельзя).</p>
<p>Инициализация потоковых переменных очень напоминает статические переменные, за
исключением, что это делается для каждого потока. В том числе это означает, что
безопасно объявлять <code>thread_local</code> переменные внутри функции. Однако
в целом <code>thread_local</code> переменные подвержены тем же проблемам, что и
статические переменные (различный порядок инициализации и т.д.).</p>
<p>У переменных <code>thread_local</code> есть тонкость, связанная с порядком удаления:
при завершении потока переменные <code>thread_local</code> будут разрушены в порядке,
обратном порядку инициализации (это штатное поведение в C++). Если же код, вызываемый
деструктором одной <code>thread_local</code> переменной, использует другую, уже разрушенную,
<code>thread_local</code> переменную этого потока, то можно получить трудно
диагностируемую проблему использование-после-удаления (use-after-free).</p>
<p class="pros"></p>
<ul>
<li>Потоковые переменные в принципе защищены от эффекта гонок (т.к. каждый поток
обычно имеет доступ только к своему экземпляру), что очень полезно для
программирования потоков.</li>
<li>Переменные <code>thread_local</code> являются единственным стандартизованным
средством для создания локальных потоковых данных.</li>
</ul>
<p class="cons"></p>
<ul>
<li>Операции с <code>thread_local</code> переменными могут неявно вызвать выполнение
другого кода, который не предполагался и его размер также может быть различным. Такое
выполнение кода может произойти как при старте потока, так и при первом использовании
в данном потоке.</li>
<li>Переменные <code>thread_local</code> являются по сути глобальными переменными
со всеми их недостатками (за исключением потокобезопасности).</li>
<li>Выделяемая для <code>thread_local</code> память зависит от количества потоков, и
её размер может быть значительным.</li>
<li>Нестатичные (не <code>static</code>) члены данных не могут быть <code>thread_local</code>.</li>
<li>Могут возникнуть ошибки использование-после-освобождения (use-after-free), если
у <code>thread_local</code> переменных сложный, запутанный деструктор. В идеале,
деструктор любой такой переменной не должен вызывать любой код (даже через вложенные вызовы),
ссылающийся на потенциально удалённые <code>thread_local</code> переменные. Хотя
это может быть трудно реализуемым.</li>
<li>Подходы к устранению проблем использования-после-удаления (use-after-free), которые
работают для глобального или статического контекстов, могут
неработать для случая <code>thread_local</code>. В частности, намеренное отсутствие
деструкторов для глобальных и статических переменных ещё можно допустить, так как
жизненный цикл таких переменных завершается при закрытии программы. Таким образом,
любая утечка памяти или ресурсов будет сразу же исправлена операционной системой.
В противоположность этому, отсутствие деструктора для <code>thread_local</code>
переменных может привести к утечке ресурсов, тем большей, чем больше завершается потоков
в процессе работы программы.</li>
</ul>
<p class="decision"></p>
<p>Переменные <code>thread_local</code> в области видимости класса или пространства имён
должны быть инициализированы константой времени компиляции (т.е. не должна использоваться
динамическая инициализация). Поэтому переменные <code>thread_local</code> в классе или
пространстве имён должны быть аннотированы как
<a href="https://en.cppreference.com/w/cpp/language/constinit"><code>constinit</code></a>
(или <code>constexpr</code>, однако это скорее как исключение):</p>
<pre> constinit thread_local Foo foo = ...;
</pre>
<p>У переменных <code>thread_local</code>, заявленных внутри функций, нет проблем
с инициализацией, однако ещё остаются риски использования-после-удаления
(use-after-free) при завершении потока. Отметим,
что возможно использовать объявленную внутри функции переменную <code>thread_local</code>
и вне функции. Для этого нужна функция доступа к переменной:</p>
<pre>Foo& MyThreadLocalFoo() {
thread_local Foo result = ComplicatedInitialization();
return result;
}
</pre>
<p>Учитывайте, что переменные <code>thread_local</code> разрушаются при завершении потока.
Если деструктор любой такой переменной использует какую-либо другую (потенциально удалённую)
переменную <code>thread_local</code>, это может привести к появлению трудно диагностируемых
ошибок использования-после-удаления (use-after-free).
Более предпочтительным является использование тривиальных типов или таких, в которых не
выполняется пользовательский код в деструкторе, чтобы минимизировать возможность доступа
к другим <code>thread_local</code> переменным.
</p>
<p>Переменные <code>thread_local</code> должны быть предпочтительным способом
определения потоко-специфичных данных.</p>
<h2 id="Classes">Классы</h2>
<p>Классы являются основным строительным блоком в C++. И, конечно же, используются
они часто. В этой секции описаны основные правила и запреты, которым нужно
следовать при использовании классов.</p>