-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1159 lines (973 loc) · 168 KB
/
index.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>
<head>
<meta charset="utf-8">
<title>Hexo</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta property="og:type" content="website">
<meta property="og:title" content="Hexo">
<meta property="og:url" content="http://example.com/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:locale">
<meta property="article:author" content="zsh">
<meta name="twitter:card" content="summary">
<link rel="alternate" href="/atom.xml" title="Hexo" type="application/atom+xml">
<link rel="shortcut icon" href="/favicon.png">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/index.min.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/fancybox/jquery.fancybox.min.css">
<meta name="generator" content="Hexo 6.3.0"></head>
<body>
<div id="container">
<div id="wrap">
<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/" id="logo">Hexo</a>
</h1>
</div>
<div id="header-inner" class="inner">
<nav id="main-nav">
<a id="main-nav-toggle" class="nav-icon"><span class="fa fa-bars"></span></a>
<a class="main-nav-link" href="/">Home</a>
<a class="main-nav-link" href="/archives">Archives</a>
</nav>
<nav id="sub-nav">
<a class="nav-icon" href="/atom.xml" title="RSS Feed"><span class="fa fa-rss"></span></a>
<a class="nav-icon nav-search-btn" title="Suche"><span class="fa fa-search"></span></a>
</nav>
<div id="search-form-wrap">
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Suche"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://example.com"></form>
</div>
</div>
</div>
</header>
<div class="outer">
<section id="main">
<article id="post-C++代码风格学习" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<div class="article-meta">
<a href="/2023/06/01/C++%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC%E5%AD%A6%E4%B9%A0/" class="article-date">
<time class="dt-published" datetime="2023-06-01T01:26:20.000Z" itemprop="datePublished">2023-06-01</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="p-name article-title" href="/2023/06/01/C++%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC%E5%AD%A6%E4%B9%A0/">C++代码风格学习</a>
</h1>
</header>
<div class="e-content article-entry" itemprop="articleBody">
<h1 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h1><ul>
<li>推荐使用 C++17,GCC 版本从 7.1 开始支持 C++17。</li>
<li>注意在使用新特性之前,考虑移植性。</li>
<li>本文不涉及命名、注释和格式的代码风格。</li>
<li>使用cpplint检查C++代码风格和潜在问题</li>
</ul>
<p>vscode 配置 C++17:<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_33899456/article/details/117966490">https://blog.csdn.net/qq_33899456/article/details/117966490</a></p>
<h2 id="1-头文件"><a href="#1-头文件" class="headerlink" title="1. 头文件"></a>1. 头文件</h2><h3 id="1-1-独立性"><a href="#1-1-独立性" class="headerlink" title="1.1 独立性"></a>1.1 独立性</h3><ul>
<li>头文件应当是自编译(并非指的是只能包含自己),即不要在头文件中添加不必要的代码</li>
<li>一个项目所有头文件都需要自编译,不应要求在构建时使用特殊条件来包含头文件</li>
<li>当头文件中有需要实例化的内联函数(便于编译器直接插入)或模板(防止编译错误)时,必须在头文件中定义</li>
</ul>
<h3 id="1-2-define-保护"><a href="#1-2-define-保护" class="headerlink" title="1.2 #define 保护"></a>1.2 #define 保护</h3><ul>
<li>所有的头文件都应该有保护措施(宏保护)</li>
<li>宏保护的定义应当是项目源代码的完整路径(唯一性),示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> FOO_BAR_BAZ_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> FOO_BAR_BAZ_H</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>上述的头文件路径为/src/bar/baz.h foo</p>
<h3 id="1-3-只引用使用的头文件"><a href="#1-3-只引用使用的头文件" class="headerlink" title="1.3 只引用使用的头文件"></a>1.3 只引用使用的头文件</h3><ul>
<li>如果一个源文件或头文件引用了其他文件定义的符号,该文件应当只包含一个头文件(该头文件提供符号的声明或定义)</li>
<li>不要依赖传递 include。有助于删除头文件时不影响其他部分。示例:foo.cc 应当包含 bar.h,尽管 foo.h 包含了 bar.h</li>
</ul>
<h3 id="1-4-前向声明(提前声明)"><a href="#1-4-前向声明(提前声明)" class="headerlink" title="1.4 前向声明(提前声明)"></a>1.4 前向声明(提前声明)</h3><ul>
<li>尽可能减少前向声明。相反去 include 需要的头文件</li>
</ul>
<h4 id="前向声明定义"><a href="#前向声明定义" class="headerlink" title="前向声明定义"></a>前向声明定义</h4><p>前向声明表示声明一个实体(func,class…)而没有相关定义,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// In a C++ source file:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">FuncInB</span><span class="params">()</span></span>;</span><br><span class="line"><span class="keyword">extern</span> <span class="type">int</span> variable_in_b;</span><br><span class="line"><span class="built_in">ABSL_DECLARE_FLAG</span>(flag_in_b);</span><br></pre></td></tr></table></figure>
<h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul>
<li>前向声明可以减少编译时间,#include 会强制编译器打开更多文件并处理更多的输入</li>
<li>前向声明可以减少不必要的重新编译。#include 会强制代码更频繁的进行编译(由于头文件发生了不相关的更改)</li>
</ul>
<h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>前向声明可以隐藏依赖项,会在更改头文件时,跳过必要的重新编译</li>
<li>与 include 相比,前向声明会使得自动化工具(CMake 等等)难以发现定义该符号的模块</li>
<li>对 lib 的更改可能会破坏前向声明,函数和模板的前向声明会妨碍管理者对 API 进行其他兼容性的更改(如扩展参数类型…)</li>
<li>从命名空间 std:: 进行前向声明会产生未定义的行为</li>
<li>很难确定是否需要一个前向声明或一个完整的#include。使用前向声明代替#include 会悄无声息的改变代码的含义</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// b.h:</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">B</span> {}; </span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">D</span> : B {}; </span><br><span class="line"></span><br><span class="line"><span class="comment">// good_user.cc:</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"b.h"</span></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(B*)</span></span>; </span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">void</span>*)</span></span>; </span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">(D* x)</span> </span>{ <span class="built_in">f</span>(x); } <span class="comment">// Calls f(B*)</span></span><br></pre></td></tr></table></figure>
<p>如果#include 替换成前向声明 B 和 D,test()将会调用 f(void *)</p>
<ul>
<li>从一个头文件声明多个符号比简单 include 头文件更啰嗦</li>
<li>启用前向声明的代码(如使用指针而不是对象)可能会使得代码变得更慢,更复杂</li>
</ul>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>尽量避免对另一个项目中定义的实体进行前向声明</p>
<h3 id="1-5-内联函数"><a href="#1-5-内联函数" class="headerlink" title="1.5 内联函数"></a>1.5 内联函数</h3><p>内联函数不应该超过 10 行</p>
<ul>
<li><strong>定义</strong>:编译器会将内联函数插入到调用的地方,而不是使用寻常的函数调用</li>
<li><strong>优点</strong>:内联函数能生成更有效的代码(内联函数较小)</li>
<li><strong>缺点</strong>:过度使用内联可能会使程序变慢,建议内联较小的函数,现代处理器,由于指令缓存的机制,更小的代码通常运行更快</li>
</ul>
<h4 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h4><ul>
<li>函数超过 10 行就不要内联,析构函数通常比看起来要长,它会隐式调用成员和基类的析构函数</li>
<li>内联带有循环或 switch 的函数并不好(除非两者通常不会执行)</li>
<li>即使将函数声明为内联函数,也不总会内联。如虚函数和递归函数(不要内联),虚函数内联是为了方便理解和维护</li>
</ul>
<h3 id="1-6-include-的名称和顺序"><a href="#1-6-include-的名称和顺序" class="headerlink" title="1.6 include 的名称和顺序"></a>1.6 include 的名称和顺序</h3><p>include 头文件的顺序为:相关头文件,C 系统头文件,C++标准头文件,其他库头文件,项目头文件</p>
<ul>
<li>项目所有头文件应当是项目源目录的子集,而不应该是UNIX目录别名(如./或../)。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/logging.h"</span></span></span><br></pre></td></tr></table></figure>
<p>上述头文件的项路径为google-awesome-project/src/base/logging.h。</p>
<p>示例:dir/foo.cc 或dir/foo_test.cc,是dir2/foo2.h的实现,那么include目录为:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">"dir2/foo2.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><stdio.h></span> <span class="comment">//C语言系统头文件</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><algorithm></span><span class="comment">//C++标准头文件,无.h</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">"base/basictypes.h"</span><span class="comment">//其他库的.h文件</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">"foo/server/bar.h"</span><span class="comment">//项目的头文件</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>标准包括空行,这种顺序有助于找出崩溃的包含,保证了构建中断优先发生在相关的头文件中。</p>
<ul>
<li>部分头文件既有C语言格式也有C++格式,引用遵循现有的规则。每一个部分的include按字母排序。示例:google-awesome-project/src/foo/internal/fooserver.cc的引用头文件:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/server/fooserver.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/types.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><vector></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/basictypes.h"</span><span class="comment">//同一部分按字母排序</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/server/bar.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"third_party/absl/flags/flag.h"</span></span></span><br></pre></td></tr></table></figure>
<h4 id="特殊条件"><a href="#特殊条件" class="headerlink" title="特殊条件"></a>特殊条件</h4><p>有时,系统特定的代码需要条件include,这样的代码include在其他include之后。示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"foo/public/fooserver.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"base/port.h"</span> <span class="comment">// For LANG_CXX11.</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> LANG_CXX11</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><initializer_list></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// LANG_CXX11</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h2 id="2-作用域"><a href="#2-作用域" class="headerlink" title="2. 作用域"></a>2. 作用域</h2><h3 id="2-1命名空间"><a href="#2-1命名空间" class="headerlink" title="2.1命名空间"></a>2.1命名空间</h3><ul>
<li>除了少数例外,将代码放在命名空间内。命名空间应根据项目名称和可能路径具有唯一的名称。不要使用using命令</li>
</ul>
<h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>命名空间将全局作用域分为多个命名作用域,有助于防止全局的命名冲突。</p>
<h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h4><ul>
<li>命名空间为大型项目中使用合理的短名称(避免了命名冲突)如:如果在全局方范围内有两个foo类,则会在编译或运行时发生冲突。将其放在两个命名空间内,如project1::foo,project2::foo。同时每个命名空间可以使用没有前缀的foo</li>
<li>内联命名空间会自动将它们的name放在封闭环境内,如:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> outer {</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">namespace</span> inner {</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>;</span><br><span class="line">} <span class="comment">// namespace inner</span></span><br><span class="line">} <span class="comment">// namespace outer</span></span><br></pre></td></tr></table></figure>
<p>outer::inner::foo()等价于outer::foo()。</p>
<h4 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>命名空间使得定义机制变得复杂(较长的前缀)</li>
<li>内联命名空间容易让人感到困惑(name并不受限于它们的命名空间)一般只用于大版本命名策略中的一部分</li>
<li>某些情况下,需要完全限定名称来重复引用符号。对于深度嵌套的命名空间(如name1::name2::foo),会增加许多杂乱代码</li>
</ul>
<h4 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h4><p>综上所述,命名空间的使用规则为:</p>
<ul>
<li>遵循命名空间的命名规则</li>
<li>使用注释打断多行命名空间,如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// In the .h file</span></span><br><span class="line"><span class="keyword">namespace</span> mynamespace {</span><br><span class="line"></span><br><span class="line"><span class="comment">// All declarations are within the namespace scope.</span></span><br><span class="line"><span class="comment">// Notice the lack of indentation.</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Foo</span><span class="params">()</span></span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace mynamespace</span></span><br></pre></td></tr></table></figure>
<ul>
<li>命名空间在include、gFlag定义/声明和其他命名空间类的声明后对整个源文件进行包装,如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// In the .cc file</span></span><br><span class="line"><span class="keyword">namespace</span> mynamespace {</span><br><span class="line"></span><br><span class="line"><span class="comment">// Definition of functions is within scope of the namespace.</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">MyClass::Foo</span><span class="params">()</span> </span>{</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace mynamespace</span></span><br></pre></td></tr></table></figure>
<p>更为复杂度的源文件示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"a.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ABSL_FLAG</span>(<span class="type">bool</span>, someflag, <span class="literal">false</span>, <span class="string">"a flag"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> mynamespace {</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> ::foo::Bar;</span><br><span class="line"></span><br><span class="line">...code <span class="keyword">for</span> mynamespace... <span class="comment">// Code goes against the left margin.</span></span><br><span class="line"></span><br><span class="line">} <span class="comment">// namespace mynamespace</span></span><br></pre></td></tr></table></figure>
<ul>
<li>在Protocol Buffers中,可以使用.proto文件中的包声明指定生成的协议消息代码的命名空间(详见Protocol Buffers)</li>
<li>不要在std命名空间中声明任何内容,如果要强行声明需要包含适当的头文件。</li>
<li>不要使用using使命名空间中所有名称都可用</li>
<li>不要在头文件的命名空间范围内使用命名空间的别名,除非是显示标记为仅内部命名空间的情况。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Forbidden -- This pollutes the namespace.</span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> foo;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Shorten access to some commonly used names in .cc files.</span></span><br><span class="line"><span class="keyword">namespace</span> baz = ::foo::bar::baz;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Shorten access to some commonly used names (in a .h file).</span></span><br><span class="line"><span class="keyword">namespace</span> librarian {</span><br><span class="line"><span class="keyword">namespace</span> impl { <span class="comment">// Internal, not part of the API.</span></span><br><span class="line"><span class="keyword">namespace</span> sidetable = ::pipeline_diagnostics::sidetable;</span><br><span class="line">} <span class="comment">// namespace impl</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">my_inline_function</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// namespace alias local to a function (or method).</span></span><br><span class="line"> <span class="keyword">namespace</span> baz = ::foo::bar::baz;</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line">} <span class="comment">// namespace librarian</span></span><br></pre></td></tr></table></figure>
<ul>
<li>不要使用内联命名空间</li>
<li>使用带有internal的命名空间标记API的内部实现部分</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// We shouldn't use this internal name in non-absl code.only use in absl internal</span></span><br><span class="line"><span class="keyword">using</span> ::absl::container_internal::ImplementationDetail;</span><br></pre></td></tr></table></figure>
<h3 id="2-2-内部链接-static"><a href="#2-2-内部链接-static" class="headerlink" title="2.2 内部链接(static)"></a>2.2 内部链接(static)</h3><p>当在.cc文件中的定义不需要在该文件之外引用时,可以将之放在未命名的命名空间或者声明为static</p>
<h4 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h4><p>所有声明都可以放在一个未命名的命名空间来进行内部使用。函数和变量也可以通过声明static来获得内部链接,这意味着你声明的任何内容都无法在另一个文件中访问。另一个文件中声明了相同的内容,两者是独立的。</p>
<h4 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h4><ul>
<li>鼓励在.cc文件中对不需要在其他文件引用的内容使用内部链接。不要在.h文件中使用内部链接</li>
<li>格式化未命名的命名空间,在终止注释中,将命名空间名称留空</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> {</span><br><span class="line">...</span><br><span class="line">} <span class="comment">// namespace</span></span><br></pre></td></tr></table></figure>
<h3 id="2-3-非成员函数、静态成员函数和全局函数"><a href="#2-3-非成员函数、静态成员函数和全局函数" class="headerlink" title="2.3 非成员函数、静态成员函数和全局函数"></a>2.3 非成员函数、静态成员函数和全局函数</h3><ul>
<li>最好将非成员函数放在命名空间内</li>
<li>很少使用完全全局函数</li>
<li>不要为了分组静态成员而使用类</li>
<li>类的静态方法通常与类的实例或类的静态数据相关</li>
</ul>
<h4 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h4><p>将非成员函数放在命名空间,可以避免污染全局命名空间</p>
<h4 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h4><p>将非成员函数和静态成员函数作为一个新类的成员可能更合理?</p>
<h4 id="总结-4"><a href="#总结-4" class="headerlink" title="总结"></a>总结</h4><ul>
<li>有时定义一个不绑定到类实例的函数是很用的。这样的函数可以是静态成员函数或非成员函数。非成员函数不依赖外部变量并且总是存在于命名空间内。不要为了分组静态成员而创建类</li>
<li>如果定义一个非成员函数,并且只在对应的.cc文件中使用,使用内部链接限制其作用域</li>
</ul>
<h3 id="2-4-局部变量"><a href="#2-4-局部变量" class="headerlink" title="2.4 局部变量"></a>2.4 局部变量</h3><ul>
<li>将函数的变量尽可能放在小的作用域内,并在声明时进行初始化</li>
<li>C++允许在任何地方声明变量,但是鼓励在局部作用域内声明变量,并尽可能接近使用的位置(方便阅读)。此外,推荐使用初始化代替声明和赋值。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> i;</span><br><span class="line">i = <span class="built_in">f</span>(); <span class="comment">// Bad -- initialization separate from declaration.</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> i = <span class="built_in">f</span>(); <span class="comment">// Good -- declaration has initialization.</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> jobs = <span class="built_in">NumJobs</span>();</span><br><span class="line"><span class="comment">// More code...</span></span><br><span class="line"><span class="built_in">f</span>(jobs); <span class="comment">// Bad -- declaration separate from use.</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> jobs = <span class="built_in">NumJobs</span>();</span><br><span class="line"><span class="built_in">f</span>(jobs); <span class="comment">// Good -- declaration immediately (or closely) followed by use.</span></span><br><span class="line"></span><br><span class="line">std::vector<<span class="type">int</span>> v;</span><br><span class="line">v.<span class="built_in">push_back</span>(<span class="number">1</span>); <span class="comment">// Prefer initializing using brace initialization.</span></span><br><span class="line">v.<span class="built_in">push_back</span>(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">std::vector<<span class="type">int</span>> v = {<span class="number">1</span>, <span class="number">2</span>}; <span class="comment">// Good -- v starts initialized.</span></span><br></pre></td></tr></table></figure>
<ul>
<li>if、while、for语句的变量通常在语句中声明,以便变量仅限于这些作用域。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (<span class="type">const</span> <span class="type">char</span>* p = <span class="built_in">strchr</span>(str, <span class="string">'/'</span>)) str = p + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<ul>
<li>es:如果变量是一个对象,每次进入作用域被创建时,都会调用构造函数,每次出作用域时,都会调用析构函数。示例如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Inefficient implementation:</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000000</span>; ++i) {</span><br><span class="line"> Foo f; <span class="comment">// My ctor and dtor get called 1000000 times each.</span></span><br><span class="line"> f.<span class="built_in">DoSomething</span>(i);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Foo f; <span class="comment">// My ctor and dtor get called once each.</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000000</span>; ++i) {</span><br><span class="line"> f.<span class="built_in">DoSomething</span>(i);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-5-静态变量和全局变量"><a href="#2-5-静态变量和全局变量" class="headerlink" title="2.5 静态变量和全局变量"></a>2.5 静态变量和全局变量</h3><ul>
<li>禁止定义具有静态存储期的对象(程序运行时一直存在的存储方式)。不鼓励在命名空间或静态类成员变量中使用动态初始化</li>
<li>如果一个全局变量的声明可以视作编译时常量(constexpr),可以视作平凡销毁(只释放内存,没有额外操作)。</li>
</ul>
<h4 id="定义-2"><a href="#定义-2" class="headerlink" title="定义"></a>定义</h4><p>每个对象都有一个存储期,与其生命周期有关。具有静态存储期的对象从他们初始化时刻起一直存在,直到程序结束。分为:1.命名空间作用域的变量(全局变量)2.类的成员静态数据或使用static声明的函数局部变量。函数局部的静态变量在程序首次声明时初始化;所有其他具有静态存储期的对象都作为程序启动的一部分进行初始化,在程序退出时销毁。</p>
<p>对象可以进行动态初始化。静态初始化总是发生在具有静态存储期的对象上(对象初始化给定的常量或表示字节都设置为0),而动态初始化在此之后才会发生。</p>
<h4 id="优点-3"><a href="#优点-3" class="headerlink" title="优点"></a>优点</h4><p>全局变量和静态变量对于许多程序非常有用:命名常量、日志…</p>
<h4 id="缺点-3"><a href="#缺点-3" class="headerlink" title="缺点"></a>缺点</h4><p>使用动态初始化或具有非平凡析构函数的全局和静态变量会使程序更加复杂,会造成难以发现的错误。析构的顺序与构造相反。当一个初始化引用具有静态存储期的另一个变量时,可能会导致在其生命周期开始之前(或结束后)访问对象。当程序启动未在退出时加入的线程时,如果它们的析构函数已经运行,这些线程可能会尝试在对象生命周期结束后访问对象。</p>
<h4 id="总结-5"><a href="#总结-5" class="headerlink" title="总结"></a>总结</h4><h5 id="对于析构函数"><a href="#对于析构函数" class="headerlink" title="对于析构函数"></a>对于析构函数</h5><p>当析构函数是平凡的时,它们的执行不受任何顺序限制(实际没有被运行);否则将面临在对象生命周期结束后访问对象的风险。因此,只允许具有静态存储期的对象是平凡析构的。基本类型(指针和整数)是平凡可析构的,以及平凡可析构类型的数组。constexpr标记的变量是平凡可析构的。示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> kNum = <span class="number">10</span>; <span class="comment">// Allowed</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">X</span> { <span class="type">int</span> n; };</span><br><span class="line"><span class="type">const</span> X kX[] = {{<span class="number">1</span>}, {<span class="number">2</span>}, {<span class="number">3</span>}}; <span class="comment">// Allowed</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">static</span> <span class="type">const</span> <span class="type">char</span>* <span class="type">const</span> kMessages[] = {<span class="string">"hello"</span>, <span class="string">"world"</span>}; <span class="comment">// Allowed</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Allowed: constexpr guarantees trivial destructor.</span></span><br><span class="line"><span class="keyword">constexpr</span> std::array<<span class="type">int</span>, 3> kArray = {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>};</span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bad: non-trivial destructor</span></span><br><span class="line"><span class="type">const</span> std::string kFoo = <span class="string">"foo"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Bad for the same reason, even though kBar is a reference (the</span></span><br><span class="line"><span class="comment">// rule also applies to lifetime-extended temporary objects).</span></span><br><span class="line"><span class="type">const</span> std::string& kBar = <span class="built_in">StrCat</span>(<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// Bad: non-trivial destructor.</span></span><br><span class="line"> <span class="type">static</span> std::map<<span class="type">int</span>, <span class="type">int</span>> kData = {{<span class="number">1</span>, <span class="number">0</span>}, {<span class="number">2</span>, <span class="number">0</span>}, {<span class="number">3</span>, <span class="number">0</span>}};</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>引用不是对象,它们不受析构性能的限制。但动态初始化的限制仍然适用。如static T& t = *new T是允许的。</li>
</ul>
<h5 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h5><p>初始化不仅仅要考虑构造函数,同时还需要考虑初始化的计算程序:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> n = <span class="number">5</span>; <span class="comment">// Fine</span></span><br><span class="line"><span class="type">int</span> m = <span class="built_in">f</span>(); <span class="comment">// ? (Depends on f)</span></span><br><span class="line">Foo x; <span class="comment">// ? (Depends on Foo::Foo)</span></span><br><span class="line">Bar y = <span class="built_in">g</span>(); <span class="comment">// ? (Depends on g and on Bar::Bar)</span></span><br></pre></td></tr></table></figure>
<p>上面程序除了第一条语句,其他语句的初始化顺序不定</p>
<p>常量初始化:初始化表达式是一个常量表达式,如果对象由构造函数调用初始化,构造函数必须指定为constexpr,示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Foo</span> { <span class="function"><span class="keyword">constexpr</span> <span class="title">Foo</span><span class="params">(<span class="type">int</span>)</span> </span>{} };</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n = <span class="number">5</span>; <span class="comment">// Fine, 5 is a constant expression.</span></span><br><span class="line"><span class="function">Foo <span class="title">x</span><span class="params">(<span class="number">2</span>)</span></span>; <span class="comment">// Fine, 2 is a constant expression and the chosen constructor is constexpr.</span></span><br><span class="line">Foo a[] = { <span class="built_in">Foo</span>(<span class="number">1</span>), <span class="built_in">Foo</span>(<span class="number">2</span>), <span class="built_in">Foo</span>(<span class="number">3</span>) }; <span class="comment">// Fine</span></span><br></pre></td></tr></table></figure>
<p>始终允许常量初始化,静态存储变量的常量初始化应该用constexpr或使用ABSL_CONST_INIT标记。任何没有标记的非局部静态变量都应该被认为具有动态初始化。<br>以下初始化是有问题的:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Some declarations used below.</span></span><br><span class="line"><span class="function"><span class="type">time_t</span> <span class="title">time</span><span class="params">(<span class="type">time_t</span>*)</span></span>; <span class="comment">// Not constexpr!</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">f</span><span class="params">()</span></span>; <span class="comment">// Not constexpr!</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Bar</span> { <span class="built_in">Bar</span>() {} };</span><br><span class="line"></span><br><span class="line"><span class="comment">// Problematic initializations.</span></span><br><span class="line"><span class="type">time_t</span> m = <span class="built_in">time</span>(<span class="literal">nullptr</span>); <span class="comment">// Initializing expression not a constant expression.</span></span><br><span class="line"><span class="function">Foo <span class="title">y</span><span class="params">(f())</span></span>; <span class="comment">// Ditto</span></span><br><span class="line">Bar b; <span class="comment">// Chosen constructor Bar::Bar() not constexpr.</span></span><br></pre></td></tr></table></figure>
<p>不建议动态初始化非局部变量。如果程序任何方面都不依赖这个初始化相对其他所有初始化的顺序,这是允许的。如:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> p = <span class="built_in">getpid</span>(); <span class="comment">// Allowed, as long as no other static variable</span></span><br><span class="line"> <span class="comment">// uses p in its own initialization.</span></span><br></pre></td></tr></table></figure>
<p>一般允许动态初始化静态局部变量</p>
<h5 id="一般情况"><a href="#一般情况" class="headerlink" title="一般情况"></a>一般情况</h5><ul>
<li>全局字符串:如果需要一个命名的全局或静态字符串常量,可以考虑使用string_view、字符数组或字符指针的constexpr变量。</li>
<li>Map、set和其他动态容器:如果需要一个静态的、固定的集合,比如一个搜索set和查找表,就不能使用标准库中动态容器作为静态变量,因为它们有非平凡的析构函数。这时可以使用简单普通类型数组(如int数组…);可以考虑使用来自absl/method/container.h的工具进行操作。</li>
<li>智能指针(std::unique_ptr,std::shared_ptr):只能指针是在析构时delete的,因此是禁止的。简单的解决方案是使用一个指向动态分配对象的普通指针,并且永远不删除。</li>
<li>自定义类型的静态变量:如果你需要自定义类型的静态常量数据,请为该类型提供一个简单的析构函数和一个constexpr构造函数</li>
<li>如果以上都失败,那么可以动态地创建一个对象,并且永远不要通过使用函数本地静态指针或引用(如static const auto& impl=*new T(args…);)来删除它</li>
</ul>
<h3 id="2-5-线程局部变量"><a href="#2-5-线程局部变量" class="headerlink" title="2.5 线程局部变量"></a>2.5 线程局部变量</h3><p>没有在函数内部声明的线程局部变量必须使用编译时常量进行初始化,并且必须使用ABSL_CONST_INIT实现。</p>
<h4 id="定义-3"><a href="#定义-3" class="headerlink" title="定义"></a>定义</h4><ul>
<li>变量可以使用thread_local说明符声明:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">thread_local</span> Foo foo = ...;</span><br></pre></td></tr></table></figure>
<ul>
<li>这样的变量实际是对象的集合,因此当不同线程访问它时,它们实际上是访问不同的对象。Thread_local变量在许多方面与静态变量相似,如:它们可以在命名空间范围、函数内部或作为静态类成员声明,但不能作为不同类成员声明</li>
<li>Thread_local变量实例的初始化类似于静态变量,不过需要为每个线程进行初始化,而不是在程序启动时进行初始化。这意味着在函数中声明的thread_local变量是安全的,但其他thread_local变量与静态变量一样受初始化顺序影响</li>
<li>Thread_local变量实例在线程终止之前不会被销毁,因此不存在静态变量的销毁顺序问题</li>
</ul>
<h4 id="优点-4"><a href="#优点-4" class="headerlink" title="优点"></a>优点</h4><ul>
<li>线程本地数据在安全的,因为只能由声明该变量的线程访问和修改,便于并发编程</li>
<li>C++11标准中,thread_local变量是创建线程本地数据的唯一标准方式。C++11中有thread_local关键字,用做线程的变量副本</li>
</ul>
<h4 id="缺点-4"><a href="#缺点-4" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>访问一个thread_local变量可能会触发执行不可预测的代码</li>
<li>thread_local实际上全局变量,有除了线程安全外其他全局变量的缺点</li>
<li>一个thread_local变量占用的内存随运行线程数增加而增加</li>
<li>非静态数据成员不能是thread_local</li>
<li>thread_local可能不如某些编译器内置函数高效</li>
</ul>
<h4 id="总结-6"><a href="#总结-6" class="headerlink" title="总结"></a>总结</h4><ul>
<li>函数内部的thread_local变量没有安全问题,无限制使用。可以使用函数作用域的thread_local变量通过定义一个公开它的函数或静态方法来模拟类或命名空间作用域的thread_local变量:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Foo& <span class="title">MyThreadLocalFoo</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">thread_local</span> Foo result = <span class="built_in">ComplicatedInitialization</span>();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>在类或命名空间作用域内的thread_local变量必须使用真正的编译时常量进行初始化(即它们必须没有动态初始化)。为了强制执行这一点,在类或命名空间作用域内的 thread_local 变量必须带有 ABSL_CONST_INIT 注释(或 constexpr):</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ABSL_CONST_INIT <span class="keyword">thread_local</span> Foo foo = ...;</span><br></pre></td></tr></table></figure>
<ul>
<li>thread_local应该优于其他定义线程本地数据的方式</li>
</ul>
<h2 id="3-类"><a href="#3-类" class="headerlink" title="3. 类"></a>3. 类</h2><h3 id="3-1-构造函数内work"><a href="#3-1-构造函数内work" class="headerlink" title="3.1 构造函数内work"></a>3.1 构造函数内work</h3><p>在构造函数内避免虚函数调用,避免失败的初始化</p>
<h4 id="定义-4"><a href="#定义-4" class="headerlink" title="定义"></a>定义</h4><p>在构造函数内可以执行任何初始化操作</p>
<h4 id="优点-5"><a href="#优点-5" class="headerlink" title="优点"></a>优点</h4><ul>
<li>不需要担心类是否已经初始化</li>
<li>通过构造函数调用完全初始化的对象可以是const的,并且可能更容易与stl容器或算法一起使用</li>
</ul>
<h4 id="缺点-5"><a href="#缺点-5" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>如果一个类调用虚函数,这些调用不会被分派到子类的实现(可能会调用父类的虚函数而不是子类的虚函数)。即使当前类没有被子类化,未来对该类的修改也可能造成该问题</li>
<li>构造函数,返回错误信息并不容易:1.程序崩溃(并不合适)2.使用异常(禁止)</li>
<li>如果构造函数内work失败,现在有一个初始化失败的对象,它可能是一个不寻常的状态,需要一个bool isvalid()状态检查机制(容易忘记调用)</li>
<li>不能获取构造函数的地址,因此无论构造函数内完成任何work,都不能轻易地移交出去(如移交到另一个线程)</li>
</ul>
<h4 id="总结-7"><a href="#总结-7" class="headerlink" title="总结"></a>总结</h4><p>构造函数永远不应该调用虚函数。如果需要在对象创建时执行一些操作,可以考虑使用工厂函数或init()方法(终止程序进行错误处理)。</p>
<h3 id="3-2-隐式类型转换"><a href="#3-2-隐式类型转换" class="headerlink" title="3.2 隐式类型转换"></a>3.2 隐式类型转换</h3><p>不要定义隐式类型转换。对转换运算符和单参数构造函数使用explicit关键字</p>
<h4 id="定义-5"><a href="#定义-5" class="headerlink" title="定义"></a>定义</h4><ul>
<li>隐式类型转换允许在需要不同类型(目标类型)时使用一个类型(源类型)对象。</li>
<li>除了编程语言自定义的隐式类型转换外,用户可以通过添加适当的成员到源类型或目标类型的类定义中来定义自己的隐式转换。源类型的隐式转换由类型转换运算符定义,如operator bool()。目标类型中隐式转换有一个只能接受源类型作为唯一参数(或唯一参数没有默认值)的构造函数定义</li>
<li>explicit关键字可以应用于构造函数或转换运算符,确保只有当目标类型在使用点是显示的时候才能使用它,如:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Foo</span> {</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Foo</span><span class="params">(<span class="type">int</span> x, <span class="type">double</span> y)</span></span>;</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Func</span><span class="params">(Foo f)</span></span>;</span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Func</span>({<span class="number">42</span>, <span class="number">3.14</span>}); <span class="comment">// Error</span></span><br></pre></td></tr></table></figure>
<p>上面error代码并不属于隐式转换,但编程语言将之视作隐式转换处理</p>
<h4 id="优点-6"><a href="#优点-6" class="headerlink" title="优点"></a>优点</h4><ul>
<li>隐式转换能够提高类型的可用性和表达性</li>
<li>隐式转换可以是重载的一种更简单的替代方式,如当一个带string_view参数的函数替代string和const char* 的单独重载时</li>
</ul>
<h4 id="缺点-6"><a href="#缺点-6" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>隐式转换会隐藏类型不匹配的错误(不容易被发现)</li>
<li>降低代码的可读性,特别是存在重载时</li>
<li>单个参数的构造函数可能意外地用做隐式类型转换</li>
<li>但一个单参数的构造函数没有使用explicit关键字时,就难以判断是用来定义隐式转换的还是忘加了</li>
<li>隐式转换会导致调用点的模糊</li>
<li>如果目标类型是隐式的,列表初始化可能会遇到相同的问题,特别是对单元素</li>
</ul>
<h4 id="总结-8"><a href="#总结-8" class="headerlink" title="总结"></a>总结</h4><ul>
<li>类定义中,类型转换运算符和可使用单参数调用的构造函数必须标记为explicit。拷贝构造和移动构造不应该标记为explicit,因为它们不执行类型转换</li>
<li>某些情况下,隐式转换是必要且适当的,特别是对于被设为可互换的类型,如当两种类型的对象相同只是底层值不同时,这种情况需要同leader交流使用隐式类型转换</li>
<li>在C++中,如果构造函数不能使用单参数调用,可以省略explicit关键字。而且,如果构造函数接受单一的std::initializer_list参数,为了支持拷贝初始化(如MyType m = {1, 2};),也应该省略explicit</li>
</ul>
<h3 id="3-3-Copyable-and-Movable类型"><a href="#3-3-Copyable-and-Movable类型" class="headerlink" title="3.3 Copyable and Movable类型"></a>3.3 Copyable and Movable类型</h3><p>类的公共API必须明确表示该类是可赋复制的、只能移动的、还是既不能复制也不能移动。如果这些操作对类是有意义的,那么应支持复制或移动操作</p>
<h4 id="定义-6"><a href="#定义-6" class="headerlink" title="定义"></a>定义</h4><ul>
<li>C++中,一个可移动类型是指一个对象可以从临时对象进行初始化和赋值</li>
<li>C++中,一个可复制对象是指一个对象可以从同一类型的任何其他对象进行初始化或赋值操作,严格要求源对象的值不发生改变。因此,可复制类型也被定义为可移动类型。如std::unique_ptr<int>是一个移动</li>
<li>对于用自定义类型,其复制行为由拷贝构造函数和拷贝赋值运算符定义。如果存在移动构造函数和移动赋值运算符,则移动行为由它们定义;否则,移动由拷贝构造和拷贝赋值运算符定义</li>
<li>某些情况下,编译器可以隐式地调用复制/移动构造函数,如按值传递对象时</li>
</ul>
<h4 id="优点-7"><a href="#优点-7" class="headerlink" title="优点"></a>优点</h4><ul>
<li>可复制和可移动对象可以按值传递和返回,API更简单、安全、通用。与传递对象时不同,通过指针或引用,不存在所有权混淆的风险、生存期、可变性和类似问题</li>
<li>复制/移动构造和赋值运算符如1clone()、copyfrom()或swap()等替代方式更容易正确定义,它们可以使用编译器生成、隐式地或使用=default。不需要堆分配或单独的初始化和赋值步骤.</li>
<li>移动操作允许隐式并且有效地传递右值</li>
</ul>
<h4 id="缺点-7"><a href="#缺点-7" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>某些类型不需要可复制性。如表示单例对象、与特定作用域相关联的对象或与对象表示紧密耦合的对象。对于多态使用的基类类型,复制操作是有风险的(不建议使用默认的复制)</li>
<li>复制构造函数会隐式调用,使得调用容易被忽略.同时可能会造成过度复制,影响性能</li>
</ul>
<h4 id="总结-9"><a href="#总结-9" class="headerlink" title="总结"></a>总结</h4><ul>
<li>每个类的public接口必须明确说明该类支持哪些复制和移动操作。(53原则要么均设定要么delete)</li>
<li>对于可复制类必须explicit声明,仅支持移动操作的类同样需要explicit声明。不支持复制和移动的累应当explicit删除,同时如果有复制和移动操作,则必须提供相应的构造函数。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Copyable</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Copyable</span>(<span class="type">const</span> Copyable& other) = <span class="keyword">default</span>;</span><br><span class="line"> Copyable& <span class="keyword">operator</span>=(<span class="type">const</span> Copyable& other) = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The implicit move operations are suppressed by the declarations above.</span></span><br><span class="line"> <span class="comment">// You may explicitly declare move operations to support efficient moves.</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MoveOnly</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">MoveOnly</span>(MoveOnly&& other) = <span class="keyword">default</span>;</span><br><span class="line"> MoveOnly& <span class="keyword">operator</span>=(MoveOnly&& other) = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The copy operations are implicitly deleted, but you can</span></span><br><span class="line"> <span class="comment">// spell that out explicitly if you want:</span></span><br><span class="line"> <span class="built_in">MoveOnly</span>(<span class="type">const</span> MoveOnly&) = <span class="keyword">delete</span>;</span><br><span class="line"> MoveOnly& <span class="keyword">operator</span>=(<span class="type">const</span> MoveOnly&) = <span class="keyword">delete</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NotCopyableOrMovable</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// Not copyable or movable</span></span><br><span class="line"> <span class="built_in">NotCopyableOrMovable</span>(<span class="type">const</span> NotCopyableOrMovable&) = <span class="keyword">delete</span>;</span><br><span class="line"> NotCopyableOrMovable& <span class="keyword">operator</span>=(<span class="type">const</span> NotCopyableOrMovable&)</span><br><span class="line"> = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The move operations are implicitly disabled, but you can</span></span><br><span class="line"> <span class="comment">// spell that out explicitly if you want:</span></span><br><span class="line"> <span class="built_in">NotCopyableOrMovable</span>(NotCopyableOrMovable&&) = <span class="keyword">delete</span>;</span><br><span class="line"> NotCopyableOrMovable& <span class="keyword">operator</span>=(NotCopyableOrMovable&&)</span><br><span class="line"> = <span class="keyword">delete</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<ul>
<li>删除/声明被忽略的情况:<ul>
<li>如果类没有private部分,那么可复制/可移动可以由任何public数据成员的可复制/可移动性决定</li>
<li>如果基类不可复制和移动,那么派生类也不可移动或复制</li>
<li>如果explicit声明或删除复制的构造函数或复制操作,其他复制操作不明显,则必须声明或删除。对移动操作也当如此</li>
</ul>
</li>
<li>对于临时对象不清楚复制/移动的结果,或者会产生其他费用,则类应当是不可移动/复制的。(尽量避免定义移动操作)</li>
<li>为了消除切割的风险(基类无法访问派生类特有的成员或功能),最好将基类定义为抽象类,即将它们的构造函数声明为protected,将析构函数声明为protected,或者给它们添加一个或多个纯虚成员函数</li>
</ul>
<h3 id="3-4-Structs-VS-Classes"><a href="#3-4-Structs-VS-Classes" class="headerlink" title="3.4 Structs VS Classes"></a>3.4 Structs VS Classes</h3><ul>
<li>struct用于表示仅承载数据的被动对象,将具有其他行为和功能的对象定义为class</li>
<li>C++中struct和class关键字几乎相同,应根据所定义的数据类型选择适当的关键字(struct默认是public,class中默认为private)</li>
<li>struct遵循规则:用于被动、承载数据的对象,没有暗示字段之间关系的不变量,所有成员都是public,构造函数、析构函数和辅助方法不涉及或强制任何不变量</li>
<li>为了与STL保持一致,在一些特殊情况可以使用struct替代class,同时两者有着不同的命名规则</li>
</ul>
<h3 id="3-5-Structs-vs-Pairs-and-Tuples"><a href="#3-5-Structs-vs-Pairs-and-Tuples" class="headerlink" title="3.5 Structs vs. Pairs and Tuples"></a>3.5 Structs vs. Pairs and Tuples</h3><ul>
<li>在某些情况下,当元素具有有意义的名称时,优先使用struct而不是pair或tuple</li>
<li>使用pair和tuple可以避免自定义类型的需要,但在阅读代码时,使用有意义的字段比使用first、second或std::get(x)更清晰,在C++14中引入了适用类型访问元组元素(类型唯一),使用有意义的字段更好</li>
<li>在泛型编程中pair和tuple对于无具体意义的字段还是可以使用</li>
</ul>
<h3 id="3-6-继承"><a href="#3-6-继承" class="headerlink" title="3.6 继承"></a>3.6 继承</h3><p>许多情况下,组合比继承更加合适,在使用继承时,声明为public</p>
<h4 id="定义-7"><a href="#定义-7" class="headerlink" title="定义"></a>定义</h4><p>当子类从基类继承时,它包含基类定义的所有数据和操作的定义。“接口继承”是从纯抽象基类(没有状态或定义方法的基类)继承,其他所有继承都是“实现继承”。</p>
<ul>
<li>实现继承指子类继承基类数据成员和方法的定义,它可以包括基类的状态和实现细节</li>
<li>接口继承是指子类继承纯抽象基类(接口),这个基类通常只包含纯虚函数的声明,没有实现代码和状态。接口继承要求子类的实现基类定义的接口,即必须提供基类中声明的所有方法的具体实现</li>
</ul>
<h4 id="优点-8"><a href="#优点-8" class="headerlink" title="优点"></a>优点</h4><ul>
<li>代码重用,派生类自动包含基类所有数据成员和成员函数,减少代码量</li>
<li>有助于编译器发现错误,编译器可以理解和检测继承的操作和错误</li>
</ul>
<h4 id="缺点-8"><a href="#缺点-8" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>代码理解较为困难,代码分布在基类和子类之间,子类无法重写非虚函数,因此子类无法更改实现</li>
<li>多重继承在实践中常常带来较高的性能开销,而且容易造成菱形继承(多态影响性能:调用虚函数首先通过虚指针跳到虚函数表,再通过偏移找到函数真实地址,再执行(无法内联造成性能降低(编译器不知道指针指向的函数地址)))</li>
</ul>
<h4 id="总结-10"><a href="#总结-10" class="headerlink" title="总结"></a>总结</h4><ul>
<li>使用继承时,通常选择public。如果需要使用private继承,可以选择在派生类中包含基类的实例作为成员变量。同时,用好final关键字,它用于标记类或成员不可被继承或重写。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Base</span> <span class="keyword">final</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下面的代码会导致编译错误,因为Derived试图继承final类Base</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Derived</span> : <span class="keyword">public</span> Base {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<ul>
<li>面向对象编程中,应当避免过度使用实现继承,多使用组合,只有类之间在语义上满足继承关系时,才应该使用继承。组合示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Engine</span> {</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Car</span> {</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> Engine engine; <span class="comment">// 使用组合将引擎作为成员变量</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> ...</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<ul>
<li>控制类成员的可见性和访问权限,设计类成员时,合理使用修饰符限制成员的访问范围(对于类自己使用的成员使用private,用于派生类使用的变量使用protected)</li>
<li>派生类重写基类的虚函数或虚析构函数时,应该明确使用override和final关键字。不应该在重写时使用virtual。使用override和final原因:如果函数不是基类虚函数的重写,将无法通过编译,有助于捕捉错误</li>
<li>允许多重继承,但是不推荐</li>
</ul>
<h3 id="3-7-运算符重载"><a href="#3-7-运算符重载" class="headerlink" title="3.7 运算符重载"></a>3.7 运算符重载</h3><h4 id="定义-8"><a href="#定义-8" class="headerlink" title="定义"></a>定义</h4><p>C++使用operator关键字声明运算符的重载,前提要求其中一个参数是用户定义的类型。同时operator关键字还允许用户定义新类型的字面量,使用operator””。下面是示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Distance</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Distance</span><span class="params">(<span class="type">double</span> meters)</span> : meters_(meters) {</span>}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">double</span> <span class="title">GetMeters</span><span class="params">()</span> <span class="type">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> meters_;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">double</span> meters_;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 定义一个用户自定义字面量 "km",用于表示千米</span></span><br><span class="line">Distance <span class="keyword">operator</span><span class="string">""</span> _km(<span class="type">long</span> <span class="type">double</span> kilometers) {</span><br><span class="line"> <span class="type">double</span> meters = kilometers * <span class="number">1000.0</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Distance</span>(meters);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 使用用户定义字面量来创建 Distance 对象</span></span><br><span class="line"> Distance d = <span class="number">2.5</span>_km;</span><br><span class="line"> std::cout << <span class="string">"Distance in meters: "</span> << d.<span class="built_in">GetMeters</span>() << std::endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="优点-9"><a href="#优点-9" class="headerlink" title="优点"></a>优点</h4><ul>
<li>运算符重载可以通过使用用户定义的类型的行为与内置类型相同,使代码更简单、易懂。</li>
</ul>
<h4 id="缺点-9"><a href="#缺点-9" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>提供一个正确、一致和可预测的运算符重载集合需要开发人员谨慎考虑其行为和语义,避免出现bug</li>
<li>运算符过度使用可能会导致代码混淆。</li>
<li>函数重载的危害同样适用于运算符重载</li>
<li>运算符重载容易欺骗阅读者,使其认为部分复杂操作是简单的内置操作</li>
<li>找到重载运算符调用位置可能需要一个能识别C++语法的搜索工具,而不是使用简单的文本检索工具如grep</li>
<li>如果在调用时使用了错误的参数类型,可能会导致不同的重载版本而不是编译错误,这可能会造成未知的bug</li>
<li>部分运算符重载具有很大风险,如&,;等</li>
<li>运算符重载在类外部定义时,存在不同文件引入同一运算符的风险,可能会造成链接器错误或未定义行为</li>
<li>UDLs允许创建新的语法形式,但可能会降低代码的可读性</li>
</ul>
<h4 id="总结-11"><a href="#总结-11" class="headerlink" title="总结"></a>总结</h4><ul>
<li>定义重载运算符时,应确保其含义明确、符合预期,与相应内置运算符保持一致</li>
<li>只在自定义类型内定义运算符,在相同的头文件、.cc文件和命名空间内定义它们。可以最小化多个定义的风险,避免将运算符定义为模板。如果定义了一个运算符,还需要定义的任何有意义的相关运算符,并确保它们的定义一致。示例:如果重载了<则重载所有的比较运算符</li>
<li>首选将不做修改的二元运算符定义为非成员函数。如果将二元运算符定义为类成员,那么隐式转换只应用于右值,而不会应用于左值。</li>
<li>不要刻意避免运算符重载,如:更喜欢定义==、=和<<,而不是Equals()、CopyFrom()和PrintTo()。相反,不要仅仅因为其他库需要运算符重载就定义运算符重载(有时使自定义比较器更好)。</li>
<li>函数重载的规则也适用于运算符重载</li>
</ul>
<h3 id="3-8-访问控制"><a href="#3-8-访问控制" class="headerlink" title="3.8 访问控制"></a>3.8 访问控制</h3><p>除非数据成员是常量,否则将类的数据成员声明为private</p>
<p>在使用Google Test时,允许测试类在.cc文件中定义的数据成员为protected,如果类定义在.h文件中,则应该将数据成员声明为private。有助于在满足测试要求的同时,保持代码的封装性和可维护性。</p>
<h3 id="3-9-声明顺序"><a href="#3-9-声明顺序" class="headerlink" title="3.9 声明顺序"></a>3.9 声明顺序</h3><ul>
<li>将类似的声明放在一起,优先声明public部分</li>
<li>类定义通常以public开始,跟protected,然后时private。省略为空的部分</li>
<li>每一部分,将类似的声明放在一起,并按以下顺序:<ul>
<li>类型和类型别名(typedef,using,enum,嵌套结构和class,友元)</li>
<li>(对struct)非静态数据成员</li>
<li>静态常量</li>
<li>工厂函数(创建和返回类或相关类层次结构的实例的函数,创建对象的一种替代方法)</li>
<li>构造函数和赋值运算符</li>
<li>析构函数</li>
<li>其他函数(static和non-static成员函数,friend函数)</li>
<li>其他数据成员(satic和non-static)</li>
</ul>
</li>
<li>不要将大型方法定义内联在类定义中。</li>
</ul>
<h2 id="4-函数"><a href="#4-函数" class="headerlink" title="4. 函数"></a>4. 函数</h2><h3 id="4-1-输入和输出"><a href="#4-1-输入和输出" class="headerlink" title="4.1 输入和输出"></a>4.1 输入和输出</h3><ul>
<li>函数返回通常由返回值确定,部分可以由输出参数(in/out parameter)返回(如使用&等)</li>
<li>使用返回值而不是输出参数更好,有助于提高可读性,并且可能会有更好的性能</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用值返回</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">add</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 使用参数返回</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">multiply</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b, <span class="type">int</span>& result)</span> </span>{</span><br><span class="line"> result = a * b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>优先返回值,其次返回引用,避免返回指针(除非可以为空)</li>
<li>函数参数既可以是输入,也可以是输出。non-optional输入参数应当为值或const引用,但non-optional输出和input/output参数应当为引用(非空)。一般来说,如果一个参数是可选且按值传递的,可以使用std::optional来表示该参数,如果该参数是非可选并且原应该使用引用类型,可以使用const指针表示。对于可选的output和input/output参数,可以使用非const指针来表示</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//不可选参数</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">calculateSum</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//可选参数</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printMessage</span><span class="params">(<span class="type">const</span> std::string& message = <span class="string">""</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> std::string& prefix = <span class="string">"-->"</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">const</span> std::string& suffix = <span class="string">"<--"</span>)</span> </span>{</span><br><span class="line"> std::cout << prefix << <span class="string">" "</span> << message << <span class="string">" "</span> << suffix << std::endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>const引用参数通常用于传递大型对象或数据结构,以避免拷贝函数。需要注意的是:const引用参数绑定到临时对象时,它们的生命周期可能比函数调用的声明周期更短,导致访问无效的内存。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">printMessage</span><span class="params">(<span class="type">const</span> std::string& message)</span> </span>{</span><br><span class="line"> std::cout << message << std::endl;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">const</span> std::string& message = <span class="string">"Hello, world!"</span>;</span><br><span class="line"> <span class="built_in">printMessage</span>(message);</span><br><span class="line"> <span class="comment">//message绑定无效内存</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//该进:</span></span><br><span class="line"><span class="comment">// Option 1: Copy the parameter</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printMessage</span><span class="params">(std::string message)</span> </span>{</span><br><span class="line"> std::cout << message << std::endl;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Option 2: Pass the parameter by const pointer</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printMessage</span><span class="params">(<span class="type">const</span> std::string* pMessage)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (pMessage != <span class="literal">nullptr</span>) {</span><br><span class="line"> std::cout << *pMessage << std::endl;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>将所有输入参数放在输出参数之前(通常建议)</li>
</ul>
<h3 id="4-2-使用较短的函数"><a href="#4-2-使用较短的函数" class="headerlink" title="4.2 使用较短的函数"></a>4.2 使用较短的函数</h3><ul>
<li>更喜欢小且集中的函数,代码更模块化,降低代码的复杂度,提高代码的可重用性</li>
<li>不应该将函数长度作为唯一的衡量标准,为了提高代码的可读性和可维护性,如果函数超过了40行,需要考虑能否将其分解为更小的函数。</li>
<li>较长的函数在未来修改可能会造成难以发现的bug,小函数更易测试和阅读</li>
<li>当碰到较长的函数时,尽力尝试将其分解为更小、更好管理的小函数</li>
</ul>
<h3 id="4-3-函数重载"><a href="#4-3-函数重载" class="headerlink" title="4.3 函数重载"></a>4.3 函数重载</h3><p>使用重载函数(包括构造函数),只有当读者查看调用点时,才能很好的了解正在发生什么,而不必确切找到正在调用哪个重载函数</p>
<h4 id="定义-9"><a href="#定义-9" class="headerlink" title="定义"></a>定义</h4><p>你可以编写一个函数,该函数接受一个const std::string&,并用另一个接受const char*的函数重载该函数。但这种情况应该考虑std::string_view。示例:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Analyze</span><span class="params">(<span class="type">const</span> std::string &text)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Analyze</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *text, <span class="type">size_t</span> textlen)</span></span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h4 id="优点-10"><a href="#优点-10" class="headerlink" title="优点"></a>优点</h4><ul>
<li>重载允许同名函数接受不同的参数。可以使代码更加直观。</li>
<li>基于const或ref限定的重载可以使实用程序代码更加可用、更加有效。</li>
</ul>
<h4 id="缺点-10"><a href="#缺点-10" class="headerlink" title="缺点"></a>缺点</h4><p>如果函数仅由参数类型重载,读者需要理解C++复杂的匹配规则才能理解函数调用的具体情况。</p>
<h4 id="总结-12"><a href="#总结-12" class="headerlink" title="总结"></a>总结</h4><p>当多个函数的重载之间没有语义上的差异时,可以对这些函数进行重载。这些重载可以在类型、限定符或参数数量上有所变化。这种函数调用,读者不需要知道选择了重载集中的哪个成员,只需要知道正在调用该集合中的某个函数。建议在头文件中单个注释记录重载集中的所有条目</p>
<h3 id="4-4-默认参数"><a href="#4-4-默认参数" class="headerlink" title="4.4 默认参数"></a>4.4 默认参数</h3><p>当保证默认值始终相同时,允许在非虚函数上使用默认参数。遵循与函数重载相同的限制,如果使用默认参数的可读性仍有以下缺点,则更推荐使用重载</p>
<h4 id="优点-11"><a href="#优点-11" class="headerlink" title="优点"></a>优点</h4><p>在编写函数时,有时需要函数使用默认值,有时需要覆盖这些默认值。应该使用默认参数,与函数重载相比,使用默认参数具有更清晰的语法,减少了模板代码。</p>
<h4 id="缺点-11"><a href="#缺点-11" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>默认参数是实现重载函数的另一种方法,因此重载函数的缺点均存在</li>
<li>虚函数调用中参数的默认值由目标对象的静态类型决定,并且不能保证给定函数的所有重写都声明相同的默认值</li>
<li>默认参数会在每个调用点重新计算,如果涉及复杂的计算或分配,这种行为会导致生成的代码冗长</li>
<li>存在默认参数的情况下,函数指针可能会令人困惑,使用函数重载可以避免这一问题</li>
</ul>
<h4 id="总结-13"><a href="#总结-13" class="headerlink" title="总结"></a>总结</h4><p>在虚函数禁止使用默认参数,虚函数调用是根据对象的动态类型确定的,默认参数值是在编译时确定的,可能会导致意外之外的行为,示例:void f(int n = count++)每次调用时,默认参数值都会递增,可能会与期望不一致</p>
<p>在其他情况下,使用默认参数可以提高函数声明的可读性,当不足以克服上述缺点时,使用重载</p>
<h3 id="4-5-尾返回类型"><a href="#4-5-尾返回类型" class="headerlink" title="4.5 尾返回类型"></a>4.5 尾返回类型</h3><p>C++中可以使用尾返回类型和前置返回类型指定函数的返回类型。</p>
<h4 id="定义-10"><a href="#定义-10" class="headerlink" title="定义"></a>定义</h4><p>C++允许两种不同形式的函数声明,旧形式为:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> x)</span></span>;</span><br></pre></td></tr></table></figure>
<p>新的声明在函数名之前使用auto关键字,参数列表后面跟尾返回类型,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> x)</span> -> <span class="type">int</span></span>;</span><br></pre></td></tr></table></figure>
<h4 id="优点-12"><a href="#优点-12" class="headerlink" title="优点"></a>优点</h4><ul>
<li>尾返回类型是显示指定lambda表达式的返回类型的唯一方法。某些情况下,编译器能够推断出lambda表达式的返回类型,即便能够推断出,显示指定也便于代码阅读</li>
<li>有时,在函数参数列表已经出现后,指定返回类型更容易,也更易读,示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T, <span class="keyword">typename</span> U></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">add</span><span class="params">(T t, U u)</span> -> <span class="title">decltype</span><span class="params">(t + u)</span></span>;</span><br></pre></td></tr></table></figure>
<p>与之相对:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T, <span class="keyword">typename</span> U></span><br><span class="line"><span class="keyword">decltype</span>(<span class="built_in">declval</span><T&>() + <span class="built_in">declval</span><U&>()) <span class="built_in">add</span>(T t, U u);</span><br></pre></td></tr></table></figure>
<h4 id="缺点-12"><a href="#缺点-12" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>语法较新,部分读者对此可能不熟悉、</li>
<li>现实中使用旧语法或混合两种语法。指定单一的版本更有利于风格统一</li>
</ul>
<h4 id="总结-14"><a href="#总结-14" class="headerlink" title="总结"></a>总结</h4><p>大多数情况下,继续使用老的函数声明方式,只有当需要使用尾返回类型时使用,如lambda表达式。尾返回类型实际中仍然很少见</p>
<h2 id="5-其他C-特征"><a href="#5-其他C-特征" class="headerlink" title="5. 其他C++特征"></a>5. 其他C++特征</h2><h3 id="5-1-所有权和智能指针"><a href="#5-1-所有权和智能指针" class="headerlink" title="5.1 所有权和智能指针"></a>5.1 所有权和智能指针</h3><p>使用动态内存分配创建的对象,最好将所有权交给一个固定所有者</p>
<h4 id="定义-11"><a href="#定义-11" class="headerlink" title="定义"></a>定义</h4><p>ownership是一种用于管理动态分配内存(和其他资源)的bookkeeping技术。确保每个分配的内存都有一个负责的所有者,该所有者保证在不需要内存时释放它。动态分配对象所有者是一个函数或对象,它确保在不再需要时删除该对象或函数。部分情况可以共享所有权,通常由最后一个所有者负责删除所有权</p>
<p>智能指针是类的指针,如通过重载*和->运算符。一些智能指针类型可用于自动化所有权bookkeeping,以确保履行这些职责。unique_ptr是一种智能指针类型,它表示动态分配对象的独占所有权;当std::unique_ptr超出作用域时,该对象将被删除。它不能被复制,但可以被移动以表示所有权转移。share_ptr是一种类型的智能指针,表示动态分配对象的共享所有权。可以复制std::share_ptr,对象的所有权在所有副本之间共享,当最后一个std::share_ptr被销毁时,对象将被删除</p>
<h4 id="优点-13"><a href="#优点-13" class="headerlink" title="优点"></a>优点</h4><ul>
<li>没有所有权逻辑,几乎不可能有效管理动态分配的内存</li>
<li>转移对象的所有权可能比复制(如果可以复制)更cheap</li>
<li>转移所有权可能比借用指针或引用更简单,它减少了在两个用户之间协调对象生命周期的需要</li>
<li>智能指针可以通过使所有权逻辑显示化、自记录和明确化来提高可读性</li>
<li>智能指针可以消除所有权bookkeeping,简化代码并排除大类错误</li>
<li>对于const对象,共享所有权可以是深拷贝的一种简单、有效的替代方法</li>
</ul>
<h4 id="缺点-13"><a href="#缺点-13" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>所有权必须通过指针(无论普通还是智能)表示和转移。指针语义比值语义更复杂,特别是API中:不仅要考虑所有权、别名、生存期和可变性等问题</li>
<li>价值语义的性能成本经常被高估,因此所有权转移的性能收益可能不足以证明可读性和复杂性成本的合理性</li>
<li>转移所有权的API使客户机进行单一的内存管理模型</li>
<li>使用智能指针的代码对于资源释放发生的位置没有那么明确</li>
<li>std::unique_str使用move语义表示所有权转移,这是一种较新的语义</li>
<li>共享所有权可以作为仔细的所有权设计的一种好的替代方案,混淆了系统设计</li>
<li>共享所有权需要在运行时进行明确的bookkeeping,成本较大</li>
<li>某些情况下,共享所有权的对象可能永远不会被删除</li>
<li>智能指针并不是纯指针的完美替代</li>
</ul>
<h4 id="总结-15"><a href="#总结-15" class="headerlink" title="总结"></a>总结</h4><ul>
<li>如果需要动态分配内存,更愿意使用分配它的代码保持所有权。如果其他代码需要访问该对象,可以考虑传递一个副本,或传递一个指针或引用而不是转移所有权。更偏向使用std::unique_str显示进行所有权转移,示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::unique_ptr<Foo> <span class="title">FooFactory</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">FooConsumer</span><span class="params">(std::unique_ptr<Foo> ptr)</span></span>;</span><br></pre></td></tr></table></figure>
<ul>
<li>在没有充分理由的情况下,不要将代码设计为使用共享所有权。其中一个原因是为了避免代价高昂的复制操作,但是只有在性能优势显著且底层对象是不可变的(如std::share_ptr<const Foo>)时才应该这么做。如果确定使用共享所有权,应当使用std::share_ptr</li>
<li>不要使用std::auto_ptr</li>
</ul>
<h3 id="5-2-右值引用"><a href="#5-2-右值引用" class="headerlink" title="5.2 右值引用"></a>5.2 右值引用</h3><ul>
<li><strong>Tips</strong>:只在定义移动构造函数与移动赋值操作时使用右值引用。不要使用std::forward</li>
</ul>
<h4 id="定义-12"><a href="#定义-12" class="headerlink" title="定义"></a>定义</h4><p>右值引用是一种只能绑定到临时对象的引用类型。语法类似传统的引用语法,如(void f(std::string && s))声明一个函数,参数是对std::string的右值引用</p>
<p>在函数参数中将’&&’应用于未限定的模板参数时,会应用特殊的模板参数推导规则。这一引用称作转发引用</p>
<h4 id="优点-14"><a href="#优点-14" class="headerlink" title="优点"></a>优点</h4><ul>
<li>定义move构造函数(一个对类类型进行右值引用的构造函数)可以移动一个值而不是复制它,如v1是一个std::vector<std::string>,那么auto v2(std::move(v1))可能会导致一些简单的指针操作,而不会复制大量数据。许多情况下可以导致性能的很大改进</li>
<li>右值引用可以实现可移动但不可复制的类型,这对于那些没有明确的复制定义但仍然希望将其作为函数参数传递、放入容器等类型非常有用</li>
<li>要高效率使用某些标准库类型,如std::unique_ptr,std::move是必须的</li>
</ul>
<h4 id="缺点-14"><a href="#缺点-14" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>右值引用是一个相对较新的特性,类似引用崩溃、移动构造函数的自动推导这样的规则都是很复杂的</li>
</ul>
<h4 id="总结-16"><a href="#总结-16" class="headerlink" title="总结"></a>总结</h4><p>只有在定义移动构造函数和移动赋值操作时使用右值引用,不要使用std::forward功能函数,可能会使用std::move表示将值从一个对象移动而不是复制到另一个对象</p>
<h3 id="5-3-友元"><a href="#5-3-友元" class="headerlink" title="5.3 友元"></a>5.3 友元</h3><ul>
<li><strong>Tips</strong>:我们允许合理使用友元类和友元函数</li>
</ul>
<p>通常友元应该定义在同一文件中,避免阅读时跳转到其他文件使用该私有成员的类。经常用到友元的地方是将FooBuilder声明为Foo的友元,以便FooBuilder正确构造Foo的内部状态,而无需将该状态暴露出来。某些情况下,将一个单元测试类声明成待测类的友元会很方便</p>
<p>友元扩大了类的封装边界。某些情况下,相对于将类成员声明为public,使用友元是更好的选择,尤其是如果你允许另一个类访问该类的私有成员时,当然,大多数类都只应该通过其提供的公有成员进行互操作</p>
<h3 id="5-4-异常"><a href="#5-4-异常" class="headerlink" title="5.4 异常"></a>5.4 异常</h3><ul>
<li><strong>Tips</strong>:我们不使用C++异常</li>
</ul>
<h4 id="优点-15"><a href="#优点-15" class="headerlink" title="优点"></a>优点</h4><ul>
<li>异常允许应用高层决定如何处理在底层嵌套函数中的failures,不用管哪些含糊且容易出错的错误代码</li>
<li>引入异常使C++与其他高级语言更一脉相承</li>
<li>有些第三方C++库依赖异常</li>
<li>异常是处理构造函数失败的唯一途径。虽然可以使用工厂函数或Init()方法代替异常,但前者要求在堆栈分配内存,后者会导致刚创建的实例处于无效状态</li>
<li>异常在测试框架中好用</li>
</ul>
<h4 id="缺点-15"><a href="#缺点-15" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>在现有函数中添加throw语句,必须检查所有调用点,要么让所有调用点都具有最低要求的异常安全保证,要么不能捕获异常并且终止程序。示例f()调用g(),g()调用h(),h抛出异常被f捕获,g必须小心处理,否则可能无法正确清理</li>
<li>更常见的是,异常会扰乱程序执行流程并难以判断,函数也许会在意料不到的地方返回,你或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了</li>
<li>异常安全需要RAII和不同编码实践,要轻松编写出正确的异常安全代码需要大量的支持机制</li>
<li>启用异常会增加二进制文件的数据,延长编译时间(可能很小),还可能加大地址空间的压力</li>
<li>滥用异常会变相鼓励开发者去捕捉不合时宜,或本来已经没法恢复的伪异常,如用户的输入不符合格式要求时,也不用抛出异常</li>
</ul>
<h4 id="总结-17"><a href="#总结-17" class="headerlink" title="总结"></a>总结</h4><ul>
<li>从表面看来,使用异常利大于弊,尤其在新项目中,但是对于现有代码,引入异常会牵连许多相关代码。如果新项目允许异常向外扩散,在跟以前未使用异常的代码整合时也是个麻烦。(主要在于Google现有的大多数C++代码都没有异常处理,引入带有异常的处理的新代码相当困难)</li>
<li>Google现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一些,迁移过程比较慢,也容易出错。我们不相信异常的使用有效替代方案,如错误代码,断言等会造成严重负担</li>
</ul>
<h3 id="5-5-noexcept"><a href="#5-5-noexcept" class="headerlink" title="5.5 noexcept"></a>5.5 noexcept</h3><p>关键字noexcept用于指定函数是否抛出异常的说明符。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//任何时候不抛出异常</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span> <span class="keyword">noexcept</span> </span>{</span><br><span class="line"> <span class="comment">// 函数体</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">//x>0时不抛出异常</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">(<span class="type">int</span> x)</span> <span class="title">noexcept</span><span class="params">(x > <span class="number">0</span>)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (x <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">"Invalid argument"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 函数体</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="定义-13"><a href="#定义-13" class="headerlink" title="定义"></a>定义</h4><p>使用noexcept用来指明函数是否抛出异常。如果一个被标记为noexcept的函数抛出了异常,程序会通过std::terminate()引发崩溃(异常逃逸)</p>
<p>noexcept是一个在编译时进行检查的运算符,用于判断一个表达式是否声明为不会抛出任何异常,并返回true或false</p>
<h4 id="优点-16"><a href="#优点-16" class="headerlink" title="优点"></a>优点</h4><ul>
<li>当将移动构造函数声明为noexcept时,某些情况下可以提高性能。示例:对于std::vector<T>::resize()这样的函数,如果T的移动构造函数声明为noexcept,它将执行移动操作而不是拷贝操作</li>
<li>在函数上使用noexcept说明符时,可以在启用异常的环境中触发编译器优化,如果编译器知道由于except说明符不会引发异常,就不必为栈展开生成额外的代码。在启用异常的环境中,编译器通常会生成与异常处理相关的代码,如栈展开和异常处理表</li>
</ul>
<h4 id="缺点-16"><a href="#缺点-16" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>在遵循本指南的项目中,如果禁用了异常,就很难确保noexcept是正确的,甚至很难定义正确性的含义</li>
<li>撤销noexcept是困难的,如果不是不可能的,因为它会消除调用者可能以依赖的保证,而这种依赖关系很难检测到</li>
</ul>
<h4 id="总结-18"><a href="#总结-18" class="headerlink" title="总结"></a>总结</h4><ul>
<li>当符合函数的预期语义时,可以使用 noexcept 来提高性能,即如果异常从函数体内部抛出,那么它代表了一个致命错误。可以假设在移动构造函数上使用 noexcept 具有明显的性能优势。如果您认为在某些其他函数上指定 noexcept 会带来显著的性能优势,请与您的项目负责人进行讨论</li>
<li>在完全禁用异常的情况下(例如大多数 Google C++ 环境),最好使用无条件的 noexcept。否则,在条件允许的情况下,使用带有简单条件的条件 noexcept 说明符,只在函数可能引发异常的少数情况下返回 false。这些条件测试可以包括对涉及操作是否可能引发异常的类型特征检查(例如,对于移动构造对象的 std::is_nothrow_move_constructible),或者对分配是否可能引发异常的检查(例如,使用 absl::default_allocator_is_nothrow 进行默认分配的标准检查)。需要注意的是,在许多情况下,引发异常的唯一可能原因是分配失败(我们认为移动构造函数除了分配失败外不应该引发异常),并且有许多应用程序将内存耗尽视为致命错误,而不是程序应尝试恢复的异常情况。即使对于其他潜在的失败情况,您也应优先考虑接口的简单性,而不是支持所有可能引发异常的场景:例如,不要编写一个依赖于哈希函数是否可能引发异常的复杂 noexcept 子句,而是简单地记录您的组件不支持哈希函数引发异常,并将其无条件声明为 noexcept</li>
</ul>
<h3 id="5-6-运行时类型识别-RTTI"><a href="#5-6-运行时类型识别-RTTI" class="headerlink" title="5.6 运行时类型识别(RTTI)"></a>5.6 运行时类型识别(RTTI)</h3><ul>
<li><strong>Tips</strong>:我们禁止使用RTTI</li>
</ul>
<h4 id="定义-14"><a href="#定义-14" class="headerlink" title="定义"></a>定义</h4><p>RTTI允许程序员在运行时识别C++类对象的类型,使用typeid或dynamic_cast完成</p>
<h4 id="优点-17"><a href="#优点-17" class="headerlink" title="优点"></a>优点</h4><ul>
<li>RTTI的标准替代需要对有问题的类层级进行修改或重构。有时这样的修改并不是我们所想要的,甚至是不可取的,尤其是在一个已经广泛使用的或者成熟的代码中</li>
<li>RTTI在某些单元测试中非常有用。比如进行工厂类测试时,用来验证一个新建对象是否为期望的动态类型。RTTI对于管理对象和派生对象的关系也很有用</li>
<li>在考虑多个抽象对象时RTTI也很好用,如:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">Base::Equal</span><span class="params">(Base* other)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">Derived::Equal</span><span class="params">(Base* other)</span> </span>{</span><br><span class="line"> Derived* that = <span class="built_in">dynamic_cast</span><Derived*>(other);</span><br><span class="line"> <span class="keyword">if</span> (that == <span class="literal">nullptr</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="缺点-17"><a href="#缺点-17" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>在运行时判断类型通常意味着设计问题。如果你需要在运行期间确定一个对象的类型,这通常说明你需要考虑重新设计你的类</li>
<li>随意地使用RTTI会使你的代码难以维护。它使得基于类型的判断树或者switch语句分布在代码各处。如果以后要进行修改,就必须检查它们</li>
</ul>
<h4 id="总结-19"><a href="#总结-19" class="headerlink" title="总结"></a>总结</h4><ul>
<li>RTTI有合理的用途但是容易被滥用,因此在使用时务必注意。在单元测试中使用RTTI,但是在其他代码中请尽量避免。尤其在新代码中,使用RTTI前务必三思。如果你的代码需要根据不同的对象类型执行不同的行为的话,请考虑使用以下两种替代方案之一:<ul>
<li>虚函数可以根据子类类型的不同而执行不同代码。这是把工作交给对象本身去处理</li>
<li>如果这一工作需要在对象之外完成,可以考虑使用双重分发的方案,如使用访问者设计模式。这就能够在对象之外进行类型判断</li>
</ul>
</li>
<li>如果程序能够保证给定的基类实例实际上都是某个派生类的实例,那么就可以自由使用dynamic_cast。在这种情况下,dynamic_cast也是一种替代方案</li>
<li>基于类型的判断树是一个很强的暗示,它说明你的代码已经偏离正轨了,以下为反例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="built_in">typeid</span>(*data) == <span class="built_in">typeid</span>(D1)) {</span><br><span class="line"> ...</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">typeid</span>(*data) == <span class="built_in">typeid</span>(D2)) {</span><br><span class="line"> ...</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">typeid</span>(*data) == <span class="built_in">typeid</span>(D3)) {</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<ul>
<li>一旦在类层级中加入新的子类,像这样的代码往往会崩溃。而且,一旦某个子类的属性改变了,你很难找到并修改所以后受影响的代码块</li>
<li>不要手工实现一个类似RTTI的方案,返回RTTI的理由同样适用于这些方案,比如带类型标签的类继承体系。而且,这些方案会掩盖你的真实意图</li>
</ul>
<h3 id="5-7-类型转换"><a href="#5-7-类型转换" class="headerlink" title="5.7 类型转换"></a>5.7 类型转换</h3><ul>
<li><strong>Tips</strong>:使用 C + + 风格的强制转换,如 static_cast <> () ,或者使用大括号初始化来转换算术类型,如 int64_t y = int64_t {1} < < 42。不要使用类似(int) x 的强制转换格式,除非强制转换为 void。只有当 T 是类类型时,才可以使用类型转换格式,如 T (x)</li>
</ul>
<h4 id="定义-15"><a href="#定义-15" class="headerlink" title="定义"></a>定义</h4><p>C++采用了有别于C的类型转换机制,对转换操作进行归类</p>
<h4 id="优点-18"><a href="#优点-18" class="headerlink" title="优点"></a>优点</h4><p>C强制转换的问题在于操作的模糊性,有时在做强制转换(如(int)3.5),有时在做类型转换(如(int)”hello”)。另外,C++类型转换在查找时更醒目</p>
<h4 id="缺点-18"><a href="#缺点-18" class="headerlink" title="缺点"></a>缺点</h4><p>C++强制转换语法冗长而繁琐</p>
<h4 id="总结-20"><a href="#总结-20" class="headerlink" title="总结"></a>总结</h4><p>不要使用C风格的类型转换,而是用C++的显示类型转换</p>
<ul>
<li>使用大括号初始化来转换算术类型(如int64_t{x})。这是最安全的方法,因为如果转换会造成信息损失,代码将不会编译。语法也很简洁</li>
<li>使用absl::implicit_cast进行安全执行隐式转换,如将Foo*转换为SuperclassOfFoo*或将Foo*转换转换为Foo*,通常会自动执行这种转换,某些情况下,需要显示向上转换,如使用条件运算符(?:)</li>
<li>当你需要显示将一个指向类的指针从子类转换为超类,或者需要显示将一个指向超类的指针转换为其子类时,使用static_cast(需要确保类型转换的正确性,不会进行类型检查)</li>
<li>使用const_cast替换const</li>
<li>使用reinterpret_cast进行指针与整数以及其他指针(包括void*)之间的不安全转换,包括从整数到指针的转换。只有确切知道自己的行为时,才使用reinterpret_cast,另外,可以考虑使用absl::bit_cast替代</li>
<li>使用absl::bit_cast将一个值的原始位转换为相同大小的不同类型,如将double的bit转换为int64_t</li>
</ul>
<h3 id="5-8-流"><a href="#5-8-流" class="headerlink" title="5.8 流"></a>5.8 流</h3><ul>
<li><strong>Tips</strong>:只在记录日志时使用流</li>
</ul>
<p>在适当地方使用流,并坚持简单的写法。仅对表示值的类型重载<<运算符进行流输出,并只输出用户可见的值,而不输出任何实现细节</p>
<h4 id="定义-16"><a href="#定义-16" class="headerlink" title="定义"></a>定义</h4><p>流是C++中的标准I/O抽象,标准头<iostream>就是一个例子,主要用于调试日子记录和测试诊断</p>
<h4 id="优点-19"><a href="#优点-19" class="headerlink" title="优点"></a>优点</h4><ul>
<li><<和>>流操作符为格式化I/O提供一个API,易于学习、可移植、可重用和可扩展。相比之下,printf甚至不支持std::string。</li>
<li>流通过std::cin、std::cout、std::cerr、std::clog为控制台I/O提供一流的支持,C API可以做到,但是需要手动缓冲输入而收到阻碍</li>
</ul>
<h4 id="缺点-19"><a href="#缺点-19" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>流使得pread()等功能函数很难执行,如果不使用printf的格式化字符串,某些格式化操作(尤其是常用的格式化字符串%.*s)用流处理性能是很低的。流不支持字符串操作符重新排序</li>
</ul>
<h4 id="总结-21"><a href="#总结-21" class="headerlink" title="总结"></a>总结</h4><ul>
<li>只有在流是最适合的工具时才使用流。通常情况下,这种情况发生在输入/输出时临时的、本地的、可读性强的,并且面向其他开发人员而不是最终用户的情况下。在使用流时要与周围代码保持一致,并与整个代码库保持一致;如果对于你的问题已经有一个成熟的工具,请使用该工具。日志记录库通常比std::cerr或std::clog更好的选择,字符串处理方面,absl/string通常比std::stringstream更好</li>
<li>避免对面向外部用户或处理不可信数据的I/O使用流。</li>
<li>如果你使用流,应该避免流API中具有状态的部分,如imbue()、xalloc()和register_callback()等函数。使用显示格式化函数(如absl::streamformat())来控制格式细节</li>
<li>当你的类型表示一个值,并且 << 运算符能够输出该值的可读字符串表示时,才对你的类型进行 << 运算符的重载作为流输出运算符。避免在 << 运算符的输出中暴露实现细节;如果你需要打印对象的内部状态进行调试,请使用具名函数(通常使用名为 DebugString() 的方法是常见的约定)</li>
</ul>
<h3 id="5-9-前置自增和自减"><a href="#5-9-前置自增和自减" class="headerlink" title="5.9 前置自增和自减"></a>5.9 前置自增和自减</h3><ul>
<li><strong>Tips</strong>:对于迭代器和其他模板对象使用前缀形式(++i)的自增,自减运算符</li>
</ul>
<h4 id="定义-17"><a href="#定义-17" class="headerlink" title="定义"></a>定义</h4><p>当一个变量被递增(++i或i++)或递减(–i或i–)并且表达式的值没有被使用时,必须决定是前递增(递减)还是后递增(递减)</p>
<h4 id="优点-20"><a href="#优点-20" class="headerlink" title="优点"></a>优点</h4><p>不考虑返回值,前递增(++i)通常比后自增(i++)效率更高。因为后置自增(或自减)需要对表达式的值i进行一次拷贝。如果i是迭代器或其他非数值类型,拷贝的代价较大</p>
<h4 id="缺点-20"><a href="#缺点-20" class="headerlink" title="缺点"></a>缺点</h4><p>C开发中,后置自增更符合自然语言语法</p>
<h4 id="总结-22"><a href="#总结-22" class="headerlink" title="总结"></a>总结</h4><p>对简单数值(非对象),两种都行,对迭代器和模板类型,使用前置自增(自减)</p>
<h3 id="5-10-const用法"><a href="#5-10-const用法" class="headerlink" title="5.10 const用法"></a>5.10 const用法</h3><ul>
<li><strong>Tips</strong>:在任何可能情况下使用const,C++11中使用constexpr更好</li>
</ul>
<h4 id="定义-18"><a href="#定义-18" class="headerlink" title="定义"></a>定义</h4><p>在声明的变量或参数前加上const用于指明变量值不可被篡改(如const int foo)。为类中的函数加上const表示该函数不会修改类成员变量的状态(如class foo{int bar(char c) const;};)</p>
<h4 id="优点-21"><a href="#优点-21" class="headerlink" title="优点"></a>优点</h4><p>更容易理解如何使用变量,编译器可以更好地进行类型检测,相应的,也能生成更好的代码,人们对编写正确代码更加自信,因为他们知道所调用的函数被限定了或不能修改变量值。即使在无锁的多线程编程中,人们也知道什么样的函数是安全的</p>
<h4 id="缺点-21"><a href="#缺点-21" class="headerlink" title="缺点"></a>缺点</h4><p>const是入侵性的;如果你向一个函数传入const变量,函数原型声明中也必须对应const参数(否则变量需要const_cast类型转换),在调用库函数时显得尤为麻烦</p>
<h4 id="总结-23"><a href="#总结-23" class="headerlink" title="总结"></a>总结</h4><ul>
<li>我们推荐在API中使用const(如在函数参数、方法和非局部变量上),只要它是有意义和准确的。这提供了一致的、主要由编译器验证的文档,说明操作可以改变哪些对象。拥有一致且可靠的区分读和写的方法对于编写线程安全的代码至关重要,并且在许多其他文档中也很有用。特别是:</li>
</ul>
<ul>
<li><ul>
<li>如果函数不会修改你传入的引用或指针类型参数,那么相应的函数参数应该分别是引用到const(const T&)或指针到const(const T*)</li>
<li>对于通过值传递的函数参数,const对调用方没有影响,因此不建议在函数声明中使用</li>
<li>将方法声明为const,除非它们改变对象的逻辑状态(或者允许用户修改该状态,如通过返回非const引用,这种情况较少),或者它们不能被安全地并发调用</li>
</ul>
</li>
</ul>
<ul>
<li>对局部变量不要使用const</li>
<li>类所有常量操作应该可以安全地并发使用。如果这是不可行的,那么类必须清楚地记录为线程不安全</li>
</ul>
<h4 id="const的位置"><a href="#const的位置" class="headerlink" title="const的位置"></a>const的位置</h4><ul>
<li>有人喜欢int const *foo,不喜欢const int *foo,他们认为前者更一致因此可读性更好,遵循了const总位于描述对象之后的原则,但是一致性原则不适用于此,”不要过度使用”的声明可以取消大部分你原本想保持的一致性。将const放在前面才更易读</li>
<li>我们提倡但不强制const在前,但要保持代码的一致性</li>
</ul>
<h3 id="5-11-constexpr用法"><a href="#5-11-constexpr用法" class="headerlink" title="5.11 constexpr用法"></a>5.11 constexpr用法</h3><ul>
<li><strong>Tips</strong>:在C++11,用constexpr来定义真正的常量,实现常量初始化</li>
</ul>
<h4 id="定义-19"><a href="#定义-19" class="headerlink" title="定义"></a>定义</h4><p>有些变量可以声明为constexpr,表明变量是真正的常量,在编译/链接时确定。有些函数和构造函数可以声明为constexpr,使得它们可以用于定义constexpr变量</p>
<h4 id="优点-22"><a href="#优点-22" class="headerlink" title="优点"></a>优点</h4><p>使用constexpr可以定义带浮点表达式的常量,不再依赖字面值;也可以定义用户自定义类型上的常量;甚至可以定义函数调用所返回的常量</p>
<h4 id="缺点-22"><a href="#缺点-22" class="headerlink" title="缺点"></a>缺点</h4><p>若过早将变量优化为constexpr变量,将来又要把它改为常规变量时,十分麻烦;当前对constexpr函数和构造函数中允许的限制可能会导致这些定义中解决的办法模糊</p>
<h4 id="总结-24"><a href="#总结-24" class="headerlink" title="总结"></a>总结</h4><p>靠constexpr特性,可以实现C++11在接口上实现真正常量的可能,不要想用constexpr来强制代码内联</p>
<h3 id="5-12-整型类型"><a href="#5-12-整型类型" class="headerlink" title="5.12 整型类型"></a>5.12 整型类型</h3><ul>
<li><strong>Tips</strong>:C++内建整型时,仅使用int,如果程序需要不同大小的变量,可以使用<stdint.h>中长度精确的整形,如int16_t,如果拿不准时,建议直接使用更大的类型</li>
</ul>
<h4 id="定义-20"><a href="#定义-20" class="headerlink" title="定义"></a>定义</h4><p>C++不指定整数类型(如int)的确切大小,short是16位,int是32位,long是32位,long long是64位</p>
<h4 id="优点-23"><a href="#优点-23" class="headerlink" title="优点"></a>优点</h4><p>保持声明的统一</p>
<h4 id="缺点-23"><a href="#缺点-23" class="headerlink" title="缺点"></a>缺点</h4><p>C++中整形大小因编译器和体系结构的不同而不同</p>
<h4 id="总结-25"><a href="#总结-25" class="headerlink" title="总结"></a>总结</h4><ul>
<li><stdint.h>定义了int16_t、uint32_t、int64_t等整形,在需要确保整形大小时可以使用它们代替short,unsigned long long等,在C整形中,只使用int, 在合适情况下,推荐使用size_t和ptrdiff_t</li>
<li>如果已知整数不会太大,常常使用int,对于大整数,使用int64_t</li>
<li>不要使用uint32_t等无符号整形,除非表示一个位组而不是一个数值,或是你需要定义二进制补码溢出。尤其不要为了指出数值永不会为负,而使用无符号类型。相反需要使用断言来保护数据</li>
<li>如果代码涉及容易返回的大小,确保类型足以应付容器各种可能的用法,拿不准时,越大越好</li>
<li>小心整型类型转换和精度提升</li>
</ul>
<h4 id="关于无符号整数"><a href="#关于无符号整数" class="headerlink" title="关于无符号整数"></a>关于无符号整数</h4><p>不推荐使用无符号类型表示非负数,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">unsigned</span> <span class="type">int</span> i = foo.<span class="built_in">Length</span>()<span class="number">-1</span>; i >= <span class="number">0</span>; --i) ...</span><br></pre></td></tr></table></figure>
<p>上述循环永远不会退出,因此,使用断言指出变量非负,而不是使用无符号整形</p>
<h3 id="5-13-64位下的可移植性"><a href="#5-13-64位下的可移植性" class="headerlink" title="5.13 64位下的可移植性"></a>5.13 64位下的可移植性</h3><ul>
<li><strong>Tips</strong>:代码应该是32位和64兼容的,处理打印,比较,结构体对齐时应当注意</li>
<li>对于某些整数类型,printf()在32位和64位系统上可移植性不是很好,printf()依赖宏扩展(<cinttypes>的PRI宏)。除非有特定合理的替代方案,否则尽量避免或升级依赖printf系列的API,相反,使用支持类型安全数字格式的库,如StrCat或用于快速简单的转换的置换,或std::ostream</li>
<li>注意sizeof(void *) != sizeof(int),如果需要一个指针大小的整数,使用intptr_t</li>
<li>注意结构体对齐,对于存储在磁盘上的结构,在64位系统上,任何带有int64_t/uint64_t成员的类/结构在默认情况下最终都是8字节对齐的,如果在32位和64位代码之间在磁盘上共享这样的结构,需要确保它们在这两种结构上的封装是相同的,大多数编译器都提供了一种改变结构对齐的方法,在gcc中是__attribute__((packed)),MSVC 则提供了 #pragma pack() 和 __declspec(align())</li>
<li>根据需要使用括号初始化来创建64位常量,如:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int64_t</span> my_value{<span class="number">0x123456789</span>};</span><br><span class="line"><span class="type">uint64_t</span> my_mask{<span class="type">uint64_t</span>{<span class="number">3</span>} << <span class="number">48</span>};</span><br></pre></td></tr></table></figure>
<h3 id="5-14-预处理宏"><a href="#5-14-预处理宏" class="headerlink" title="5.14 预处理宏"></a>5.14 预处理宏</h3><ul>
<li>避免定义宏,特别是在头文件中,更推荐用内联函数、枚举和常量变量。使用特定于项目的前缀命名宏。不要使用宏定义C++API片段</li>
<li>宏意味着用户看到的代码和编译器看到的不一样,可能会导致意外的行为</li>
<li>在定义C++API各个部分时,问题尤其严重。当开发人员错误使用该接口时,编译器发出的错误消息都必须解释宏是如何形成该接口的。重构和分析工具在更新新接口方面面临着巨大的困难,因此,禁止以这种方式使用宏,如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">class</span> <span class="title">WOMBAT_TYPE</span><span class="params">(Foo)</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">EXPAND_PUBLIC_WOMBAT_API</span>(Foo)</span><br><span class="line"></span><br><span class="line"> <span class="built_in">EXPAND_WOMBAT_COMPARISONS</span>(Foo, ==, <)</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<ul>
<li>在C++中,宏没有那么必要,可以使用内联函数进行替代,不要使用宏存储常量,而是使用常量变量,不要使用宏缩写长变量名,使用引用,不要使用宏来有条件地编译代码…. </li>
<li>下面是宏的相关使用规则:<ul>
<li>不要在.h文件中定义宏</li>
<li>在使用宏之前定义#define,之后立即取消定义宏#undef</li>
<li>用自己的宏替代现有宏之前,不要只用#undef,建议选择一个唯一的name来命名自己的宏</li>
<li>尽量不要使用扩展到不平衡的C++结构的宏(),如果实在需要,使用文档记录</li>
<li>不要使用##生成函数/类/变量名</li>
</ul>
</li>
</ul>
<h3 id="5-15-0和nullptr-x2F-NULL"><a href="#5-15-0和nullptr-x2F-NULL" class="headerlink" title="5.15 0和nullptr/NULL"></a>5.15 0和nullptr/NULL</h3><ul>
<li>指针使用nullptr,字符使用’\0’(不要使用常量0)</li>
<li>指针使用nullptr,它提供类型安全性</li>
</ul>
<h3 id="5-16-sizeof"><a href="#5-16-sizeof" class="headerlink" title="5.16 sizeof"></a>5.16 sizeof</h3><ul>
<li><strong>Tips</strong>:推荐使用sizeof(varname)替换sizeof(type)</li>
<li>使用sizeof(varname)替代sizeof(type),当变量类型改变时,sizeof也随之改变,示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">MyStruct data;</span><br><span class="line"><span class="built_in">memset</span>(&data, <span class="number">0</span>, <span class="built_in">sizeof</span>(data));</span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">memset</span>(&data, <span class="number">0</span>, <span class="built_in">sizeof</span>(MyStruct));</span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (raw_size < <span class="built_in">sizeof</span>(<span class="type">int</span>)) {</span><br><span class="line"> <span class="built_in">LOG</span>(ERROR) << <span class="string">"compressed record not big enough for count: "</span> << raw_size;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="5-17-类型推导"><a href="#5-17-类型推导" class="headerlink" title="5.17 类型推导"></a>5.17 类型推导</h3><ul>
<li><strong>Tips</strong>:用auto替换繁琐的类型名,只要可读性好就继续用,不要用在局部变量之外的地方</li>
</ul>
<h4 id="定义-21"><a href="#定义-21" class="headerlink" title="定义"></a>定义</h4><p>变量声明为auto,它的类型会自动匹配为初始化表达式的类型,以下是具体的使用地方</p>
<ul>
<li>函数模板参数推导<ul>
<li>可以在不使用显示模板参数情况下调用函数模板。编译器从函数参数的类型中推导参数,如下示例:</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(T t)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">f</span>(<span class="number">0</span>); <span class="comment">// Invokes f<int>(0)</span></span><br></pre></td></tr></table></figure>
<ul>
<li>变量声明<ul>
<li>变量声明使用auto替代类型,编译器从变量初始值推导出类型,遵循函数模板参数推导相同的规则(前提是使用圆括号()而不是花括号{})</li>
<li>auto可以用const限定,可以用作指针或引用类型的一部分,但不能作为模板参数,使用decltype(auto)推断的类型是decltype的结果,示例:</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> a = <span class="number">42</span>; <span class="comment">// a is an int</span></span><br><span class="line"><span class="keyword">auto</span>& b = a; <span class="comment">// b is an int&</span></span><br><span class="line"><span class="keyword">auto</span> c = b; <span class="comment">// c is an int</span></span><br><span class="line"><span class="keyword">auto</span> d{<span class="number">42</span>}; <span class="comment">// d is an int, not a std::initializer_list<int></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">(T arg)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> value = <span class="number">42</span>;</span><br><span class="line"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) result = <span class="built_in">foo</span>(value); <span class="comment">// 推断为模板实例化后的类型</span></span><br></pre></td></tr></table></figure>
<ul>
<li>函数返回类型推导<ul>
<li>auto和decltype(auto)也可以用来替代函数的返回类型,编译器从函数体中return语句中推断出返回类型</li>
<li>lambda表达式返回类型也是使用同样方法推导出来,但是是通过省略返回类型而不是通过显示auto触发的,函数后面的返回类型语法在返回类型位置也使用auto,但并不依赖类型推导,只是显示返回类型的替代语法,示例:</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">f</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="number">0</span>; } <span class="comment">// The return type of f is int</span></span><br></pre></td></tr></table></figure>
<ul>
<li>泛用lambda表达式<ul>
<li>lambda表达式可以使用auto关键字代替它的一个或多个参数类型,使得lambda的调用操作符成为一个函数模板,示例:</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Sort `vec` in decreasing order</span></span><br><span class="line">std::<span class="built_in">sort</span>(vec.<span class="built_in">begin</span>(), vec.<span class="built_in">end</span>(), [](<span class="keyword">auto</span> lhs, <span class="keyword">auto</span> rhs) { <span class="keyword">return</span> lhs > rhs; });</span><br></pre></td></tr></table></figure>
<ul>
<li>lambda初始化捕获<ul>
<li>lambda捕获可以有显示的初始化,可以用来声明全新的变量,而不仅仅是捕获现有变量,这种语法不允许指定类型,示例:</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[x = <span class="number">42</span>, y = <span class="string">"foo"</span>] { ... } <span class="comment">// x is an int, and y is a const char*</span></span><br></pre></td></tr></table></figure>
<ul>
<li>结构化绑定<ul>
<li>当使用auto声明一个tuple、struct或数组时,可以为单个元素指定名称,而不是为整个对象指定名称;这些名称称为结构化绑定,这种语法无法指定封闭对象或单个名称的类型</li>
<li>auto 也可以使用 const、 & 和 & & 限定,但请注意,这些限定符在技术上适用于匿名 tuple/struct/array,而不是单个绑定。确定绑定类型的规则相当复杂</li>
</ul>
</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> [iter, success] = my_map.<span class="built_in">insert</span>({key, value});</span><br><span class="line"><span class="keyword">if</span> (!success) {</span><br><span class="line"> iter->second = value;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="优点-24"><a href="#优点-24" class="headerlink" title="优点"></a>优点</h4><ul>
<li>简化C++变量声明</li>
<li>有时候类型推导更安全,可以减少副本或类型转换</li>
</ul>
<h4 id="缺点-24"><a href="#缺点-24" class="headerlink" title="缺点"></a>缺点</h4><p>当类型是显式的时候,C + + 代码通常更加清晰,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> foo = x.<span class="built_in">add_foo</span>();</span><br><span class="line"><span class="keyword">auto</span> i = y.<span class="built_in">Find</span>(key);</span><br></pre></td></tr></table></figure>
<ul>
<li>如果 y 的类型不是很清楚,或者如果 y 在很多行之前被声明,那么结果类型是什么可能并不清楚</li>
<li>如果一个推导出的类型被用作接口的一部分,那么程序员可能会更改它的类型,而只是打算更改它的值,从而导致比预期的更彻底的 API 更改。</li>
</ul>
<h4 id="总结-26"><a href="#总结-26" class="headerlink" title="总结"></a>总结</h4><p>基本规则是: 使用类型演绎只是为了使代码更清晰或更安全,而不是仅仅为了避免编写显式类型的不便</p>
<p>这些原则适用于所有形式的类型演绎,但细节各不相同,如下面各节所述</p>
<ul>
<li>函数模板参数推导</li>
</ul>
<p>使用类型推导,编译器可以根据函数模板调用时提供的函数参数自动确定模板参数的类型。简化了函数模板的使用,用户不需要在每次调用函数时显示指定模板参数</p>
<ul>
<li>局部变量类型推导</li>
</ul>
<p>对局部变量,可以使用类型推导来消除明显或不相关的类型信息,使代码更加清晰,读者可以关注代码中有意义的部分,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">std::unique_ptr<WidgetWithBellsAndWhistles> widget =</span><br><span class="line"> std::<span class="built_in">make_unique</span><WidgetWithBellsAndWhistles>(arg1, arg2);</span><br><span class="line">absl::flat_hash_map<std::string,</span><br><span class="line"> std::unique_ptr<WidgetWithBellsAndWhistles>>::const_iterator</span><br><span class="line"> it = my_map_.<span class="built_in">find</span>(key);</span><br><span class="line">std::array<<span class="type">int</span>, 6> numbers = {<span class="number">4</span>, <span class="number">8</span>, <span class="number">15</span>, <span class="number">16</span>, <span class="number">23</span>, <span class="number">42</span>};</span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> widget = std::<span class="built_in">make_unique</span><WidgetWithBellsAndWhistles>(arg1, arg2);</span><br><span class="line"><span class="keyword">auto</span> it = my_map_.<span class="built_in">find</span>(key);</span><br><span class="line">std::array numbers = {<span class="number">4</span>, <span class="number">8</span>, <span class="number">15</span>, <span class="number">16</span>, <span class="number">23</span>, <span class="number">42</span>};</span><br></pre></td></tr></table></figure>
<p>类型有时包含有用信息和模板文件的混合,如上:类型是一个迭代器,许多上下文中,容器类型甚至键类型不相关,值的类型可能是有用的。这种情况,可以用显示类型定义局部变量来传递信息,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="keyword">auto</span> it = my_map_.<span class="built_in">find</span>(key); it != my_map_.<span class="built_in">end</span>()) {</span><br><span class="line"> WidgetWithBellsAndWhistles& widget = *it->second;</span><br><span class="line"> <span class="comment">// Do stuff with `widget`</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果一个更简单的选项可行,不要使用decltype(auto),因为它是一个相当模糊的特性,所以在代码清晰度方面有很高成本</p>
<ul>
<li>返回类型推导</li>
</ul>
<p>应谨慎使用lambda的auto参数类型,因为实际类型是由调用lambda的代码决定的,而不是由lambda定义决定的。因此,显示类型几乎总是更清晰的,除非lambda被显式调用的位置非常接近它的定义位置,或者lambda被传递到一个接口,所以很明显它最终将用什么参数来调用(如上面的std::sort())</p>
<ul>
<li>lambda初始化捕获</li>
</ul>
<p>初始化捕获由更具体的样式规则覆盖,它很大程度取代了类型推到的一般规则</p>
<ul>
<li>结构化绑定</li>
</ul>
<p>与其他类型推导不同,结构化绑定实际上可以通过给较大对象的元素赋予有意义的名称给读者提供额外信息。这意味着结构化绑定声明可以提供比显式类型更好的可读性,即使在auto不能提供可读性的情况也是如此。结构化绑定在对象是pair或tuple时特别有用,它们开始时没有有意义的字段名,但要注意,通常不该用pair或tuple,除非预先存在的API强制这样做</p>
<p>如果绑定的对象是一个struct,那么有时候提供更具体的名称可能会有所帮助,但要记得,意味着对读者来说,名称比字段名称更难识别。建议使用注释来指定基础字段的名称,使用与函数参数注释相同的语法,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> [<span class="comment">/*field_name1=*/</span>bound_name1, <span class="comment">/*field_name2=*/</span>bound_name2] = ...</span><br></pre></td></tr></table></figure>
<h3 id="5-18-类模板参数推导"><a href="#5-18-类模板参数推导" class="headerlink" title="5.18 类模板参数推导"></a>5.18 类模板参数推导</h3><p>类模板参数推导只能在明确选择支持该特性的模板中使用</p>
<h4 id="定义-22"><a href="#定义-22" class="headerlink" title="定义"></a>定义</h4><ul>
<li>类模板参数推导(CTAD)发生在一个变量声明了一个命名模板的类型,并且没有提供模板参数列表。示例:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::array a = {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}; <span class="comment">// `a` is a std::array<int, 3></span></span><br></pre></td></tr></table></figure>
<ul>
<li>编译器使用模板的推导从初始化中推导参数,这些推导可以是显式或隐式的</li>
<li>显示推导看起来像是带有尾返回类型的函数声明,只是没有前导auto,而且函数名是模板的名称。如上面std::array的推导指南,如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> std {</span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>, <span class="keyword">class</span>... U></span><br><span class="line"><span class="built_in">array</span>(T, U...) -> std::array<T, 1 + <span class="keyword">sizeof</span>...(U)>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>主模板中的构造函数也隐式定义了推导指南</li>
<li>当声明一个依赖CTAD的变量时,编译器使用构造函数重载解析规则选择一个推断指南,并且该指南的返回类型将成为变量的类型</li>
</ul>
<h4 id="优点-25"><a href="#优点-25" class="headerlink" title="优点"></a>优点</h4><p>CTAD有时允许从代码中省略样板文件</p>
<h4 id="缺点-25"><a href="#缺点-25" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>在C++17引入CTAD之前编写的构造函数可能会生成不良行为或明显错误的隐式推断指南。对于早期的构造函数的作者无法了解他们的构造函数对CTAD可能会造成的任何问题。为了修复这些问题,添加显示推断指南可能会破坏依赖于隐式推断指南的现有代码</li>
<li>CTAD还有许多与auto相同的缺点</li>
</ul>
<h4 id="总结-27"><a href="#总结-27" class="headerlink" title="总结"></a>总结</h4><ul>
<li>不要使用给定模板的CTAD,除非模板的维护者提供至少一个明确的显示推断指南。同时默认假设std命令空间中所有模板都选择支持CTAD。</li>
<li>使用CTAD必须遵循类型推导的规则</li>
</ul>
<h3 id="5-19-指定初始化器"><a href="#5-19-指定初始化器" class="headerlink" title="5.19 指定初始化器"></a>5.19 指定初始化器</h3><p>只使用C++20的指定初始值设定项</p>
<h4 id="定义-23"><a href="#定义-23" class="headerlink" title="定义"></a>定义</h4><p>指定初始化器是一种语法,允许通过显示命令聚合的字段来初始化,如下:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Point</span> {</span><br><span class="line"> <span class="type">float</span> x = <span class="number">0.0</span>;</span><br><span class="line"> <span class="type">float</span> y = <span class="number">0.0</span>;</span><br><span class="line"> <span class="type">float</span> z = <span class="number">0.0</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">Point p = {</span><br><span class="line"> .x = <span class="number">1.0</span>,</span><br><span class="line"> .y = <span class="number">2.0</span>,</span><br><span class="line"> <span class="comment">// z will be 0.0</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>显示列出的字段将按指定方式初始化,其他字段将按与传统聚合初始化表达式(如Point{1.0,2.0})相同的方式初始化</p>
<h4 id="优点-26"><a href="#优点-26" class="headerlink" title="优点"></a>优点</h4><p>指定的初始值设定项可以使聚合表达式变得方便和高度可读,特别是对复杂的结构</p>
<h4 id="缺点-26"><a href="#缺点-26" class="headerlink" title="缺点"></a>缺点</h4><p>C + + 标准中的规则比 C 和编译器扩展中的规则更严格,要求指定的初始值设定项的出现顺序与结构定义中字段的出现顺序相同。所以在上面的例子中,根据 C + + 20,初始化 x 然后 z 是合法的,但是初始化 y 然后 x 是不合法的。</p>
<h4 id="总结-28"><a href="#总结-28" class="headerlink" title="总结"></a>总结</h4><p>只在与 C + + 20标准兼容的形式中使用指定的初始值设定项: 初始值设定项的顺序与结构定义中出现的相应字段的顺序相同</p>
<h3 id="5-20-lambda表达式"><a href="#5-20-lambda表达式" class="headerlink" title="5.20 lambda表达式"></a>5.20 lambda表达式</h3><ul>
<li><strong>Tips</strong>:在适当的地方使用lambda表达式,当lambda将逃逸当前作用域,使用显示捕获</li>
</ul>
<h4 id="定义-24"><a href="#定义-24" class="headerlink" title="定义"></a>定义</h4><p>Lambda 表达式是创建匿名函数对象的一种简明方法。当将函数作为参数传递时,它们通常很有用。例如:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">std::<span class="built_in">sort</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), [](<span class="type">int</span> x, <span class="type">int</span> y) {</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Weight</span>(x) < <span class="built_in">Weight</span>(y);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>它们还允许显式地通过名称或隐式地使用默认捕获从封闭范围捕获变量。显式捕获要求列出每个变量,作为值或引用捕获:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> weight = <span class="number">3</span>;</span><br><span class="line"><span class="type">int</span> sum = <span class="number">0</span>;</span><br><span class="line"><span class="comment">// Captures `weight` by value and `sum` by reference.</span></span><br><span class="line">std::for_each(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), [weight, &sum](<span class="type">int</span> x) {</span><br><span class="line"> sum += weight * x;</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>默认捕获隐式捕获 lambda 主体中引用的任何变量,如果使用了任何成员,则包括:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> std::vector<<span class="type">int</span>> lookup_table = ...;</span><br><span class="line">std::vector<<span class="type">int</span>> indices = ...;</span><br><span class="line"><span class="comment">// Captures `lookup_table` by reference, sorts `indices` by the value</span></span><br><span class="line"><span class="comment">// of the associated element in `lookup_table`.</span></span><br><span class="line">std::<span class="built_in">sort</span>(indices.<span class="built_in">begin</span>(), indices.<span class="built_in">end</span>(), [&](<span class="type">int</span> a, <span class="type">int</span> b) {</span><br><span class="line"> <span class="keyword">return</span> lookup_table[a] < lookup_table[b];</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>变量捕获还可以有一个显式的初始值设定项,它可以用于按值捕获仅移动变量,或者用于普通引用或值捕获不能处理的其他情况:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::unique_ptr<Foo> foo = ...;</span><br><span class="line">[foo = std::<span class="built_in">move</span>(foo)] () {</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这样的捕获(通常称为“ init 捕获”或“广义 lambda 捕获”)实际上不需要从封闭范围中“捕获”任何东西,甚至不需要从封闭范围中获得名称; 这种语法是定义 lambda 对象成员的一种完全通用的方法:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[foo = std::<span class="built_in">vector</span><<span class="type">int</span>>({<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>})] () {</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>带有初始化器的捕获变量的类型推导规则与auto使用相同的规则</p>
<h4 id="优点-27"><a href="#优点-27" class="headerlink" title="优点"></a>优点</h4><ul>
<li>传函数给STL算法,lambda更简单,可读性好</li>
<li>适当使用默认捕获可以消除冗余并突出显示默认情况下的重要异常</li>
<li>lambda、std::function和std::bind可以组合使用,作为一种通用的回调机制;使得编写将绑定函数作为参数的函数变得容易</li>
</ul>
<h4 id="缺点-27"><a href="#缺点-27" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>lambda表达式中变量捕获可能是悬空指针错误的源头</li>
<li>lambda可能会失控,层层嵌套的匿名函数难以阅读</li>
</ul>
<h4 id="总结-29"><a href="#总结-29" class="headerlink" title="总结"></a>总结</h4><ul>
<li>在适当地方使用lambda表达式,格式如下:</li>
<li>如果lambda可能逃脱当前作用域,更喜欢使用显示捕获,如下:</li>
</ul>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> Foo foo;</span><br><span class="line"> ...</span><br><span class="line"> executor-><span class="built_in">Schedule</span>([&] { <span class="built_in">Frobnicate</span>(foo); })</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"><span class="comment">// BAD! The fact that the lambda makes use of a reference to `foo` and</span></span><br><span class="line"><span class="comment">// possibly `this` (if `Frobnicate` is a member function) may not be</span></span><br><span class="line"><span class="comment">// apparent on a cursory inspection. If the lambda is invoked after</span></span><br><span class="line"><span class="comment">// the function returns, that would be bad, because both `foo`</span></span><br><span class="line"><span class="comment">// and the enclosing object could have been destroyed.</span></span><br></pre></td></tr></table></figure>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> Foo foo;</span><br><span class="line"> ...</span><br><span class="line"> executor-><span class="built_in">Schedule</span>([&foo] { <span class="built_in">Frobnicate</span>(foo); })</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"><span class="comment">// BETTER - The compile will fail if `Frobnicate` is a member</span></span><br><span class="line"><span class="comment">// function, and it's clearer that `foo` is dangerously captured by</span></span><br><span class="line"><span class="comment">// reference.</span></span><br></pre></td></tr></table></figure>
<ul>
<li>只有当 lambda 的生命周期明显短于任何潜在捕获时,才使用引用默认捕获([ & ])</li>
<li>使用默认值捕获([ = ])仅仅作为绑定一个短 lambda 的几个变量的方法,其中捕获的变量集一目了然,并且不会导致隐式捕获这个变量集。</li>
<li>仅使用捕获来实际捕获来自封闭范围的变量。不要使用带有初始值设定项的捕获来引入新名称,或者实质上更改现有名称的含义</li>
<li>有关指定参数和返回类型的指导,请参阅类型推导一节</li>
</ul>
<h3 id="5-21-模板元编程"><a href="#5-21-模板元编程" class="headerlink" title="5.21 模板元编程"></a>5.21 模板元编程</h3><ul>
<li><strong>Tips</strong>:避免复杂的模板编程</li>
</ul>
<h4 id="定义-25"><a href="#定义-25" class="headerlink" title="定义"></a>定义</h4><p>模板元编程是一种利用C++模板实例化机制进行编译时计算的技术,模板元编程的主要优势在于能够在编译时进行静态计算,避免了运行时的开销,并能够实现更高度的抽象和泛化</p>
<h4 id="优点-28"><a href="#优点-28" class="headerlink" title="优点"></a>优点</h4><p>模板超编程允许非常灵活的界面,类型安全和高性能。像 GoogleTest、 std: : tuple、 std: : function 和 Boost 这样的工具。</p>
<h4 id="缺点-28"><a href="#缺点-28" class="headerlink" title="缺点"></a>缺点</h4><ul>
<li>以复杂方式使用模板的代码通常是不可读的,并且难以调试或维护。</li>
<li>模板元编程干扰了大规模的重构,使得重构工具的工作更加困难。首先,模板代码在多个上下文中展开,很难验证转换是否在所有上下文中都有意义</li>
</ul>
<h4 id="总结-30"><a href="#总结-30" class="headerlink" title="总结"></a>总结</h4><ul>
<li>模板编程最好只用在少量的基础组件, 基础数据结构上, 因为模板带来的额外的维护成本会被大量的使用给分担掉</li>
<li>如果你使用模板编程, 你必须考虑尽可能的把复杂度最小化, 并且尽量不要让模板对外暴露. 你最好只在实现里面使用模板, 然后给用户暴露的接口里面并不使用模板, 这样能提高你的接口的可读性. 并且你应该在这些使用模板的代码上写尽可能详细的注释. 你的注释里面应该详细的包含这些代码是怎么用的, 这些模板生成出来的代码大概是什么样子的. 还需要额外注意在用户错误使用你的模板代码的时候需要输出更人性化的出错信息.</li>
</ul>
<h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p>谷歌 C++指南:<a target="_blank" rel="noopener" href="https://google.github.io/styleguide/cppguide.html">https://google.github.io/styleguide/cppguide.html</a></p>
<p>ChatGPT:<a target="_blank" rel="noopener" href="https://chat.openai.com/">https://chat.openai.com/</a></p>
<p>cpplint下载和使用:<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/79913216">https://zhuanlan.zhihu.com/p/79913216</a></p>
<style>
p{text-indent:2em}
</style>
</div>
<footer class="article-footer">
<a data-url="http://example.com/2023/06/01/C++%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC%E5%AD%A6%E4%B9%A0/" data-id="clirkgb2u0002p4te976b8ewd" data-title="C++代码风格学习" class="article-share-link"><span class="fa fa-share">Teilen</span></a>
<ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/C/" rel="tag">C++</a></li></ul>
</footer>