-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
4881 lines (4656 loc) · 721 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Archlinux装机笔记</title>
<url>/2022/10/24/Archlinux%E8%A3%85%E6%9C%BA%E7%AC%94%E8%AE%B0/</url>
<content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><strong>重要数据·务必备份</strong>!固态硬盘报废,连带 Linux 无了,只能重装。恐将来也会频繁装机,故写一篇笔记,希望一劳永逸。</p>
<span id="more"></span>
<h2 id="进入桌面前"><a href="#进入桌面前" class="headerlink" title="进入桌面前"></a>进入桌面前</h2><h3 id="准备装机盘"><a href="#准备装机盘" class="headerlink" title="准备装机盘"></a>准备装机盘</h3><ol>
<li>在 <a href="https://archlinux.org/download/">archlinux</a> 找到镜像源,下载 ISO。</li>
<li>用 <a href="https://rufus.ie/zh/">Rufus</a> 之类的刷 U 盘。<ul>
<li>MBR 分区</li>
<li>ISO 模式</li>
</ul>
</li>
</ol>
<h3 id="引导进装机盘"><a href="#引导进装机盘" class="headerlink" title="引导进装机盘"></a>引导进装机盘</h3><p>华硕主板的快捷键是 <code>F11</code>。如果黑屏,将 DP 线换成 HDMI。</p>
<h3 id="archinstall"><a href="#archinstall" class="headerlink" title="archinstall"></a>archinstall</h3><ol>
<li>进临时操作系统之后,连网线,运行 <code>archinstall</code>。</li>
<li>选镜像源(mirror region)</li>
<li>选择格式化硬盘</li>
<li>创建 superuser。</li>
<li>其他选项都检查一下。archinstall 可能会有大幅的更新。</li>
<li>install! </li>
<li>最后提示 chroot,在里面 <code>systemctl enable sddm</code>。</li>
</ol>
<h3 id="卡-wayland"><a href="#卡-wayland" class="headerlink" title="卡 wayland"></a>卡 wayland</h3><p>N 卡 + nvidia 专有驱动 + wayland = 黑屏</p>
<p>在 <code>~/.local/share/sddm/wayland-session.log</code> 可查看 log。</p>
<ol>
<li>先用 X11 登陆,把环境都配置好再回来。</li>
<li>添加内核参数:<code>nvidia-drm.modeset=1</code>。这是 wayland 要求的。<ul>
<li>适用于 systemd-boot:<code>kate /boot/loader/entries/xxxx.conf</code>。追加参数。</li>
</ul>
</li>
<li>已经能用了,但是 BUG 很多又卡顿,Fuck Nvidia。遂放弃,或者<a href="https://forum.manjaro.org/t/installing-video-nvidia-causes-kde-sddm-to-fail-to-load/110395/6">参考</a></li>
</ol>
<h2 id="进入桌面后"><a href="#进入桌面后" class="headerlink" title="进入桌面后"></a>进入桌面后</h2><h3 id="添加-sudo-免密"><a href="#添加-sudo-免密" class="headerlink" title="添加 sudo 免密"></a>添加 sudo 免密</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">su</span><br><span class="line">vim /etc/sudoers.d/00_codesire</span><br><span class="line"><span class="comment"># 内容如下</span></span><br><span class="line">codesire ALL=(ALL:ALL) NOPASSWD: ALL</span><br></pre></td></tr></table></figure>
<h3 id="调整快捷键"><a href="#调整快捷键" class="headerlink" title="调整快捷键"></a>调整快捷键</h3><ol>
<li>Konsole</li>
<li>System Settings</li>
<li>KRunner</li>
<li>(后面安装的)Flameshot: <code>/usr/bin/flameshot gui</code></li>
</ol>
<h3 id="安装-yay"><a href="#安装-yay" class="headerlink" title="安装 yay"></a>安装 yay</h3><p>利用 git 下载预编译版安装。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo pacman -S git</span><br><span class="line"><span class="built_in">cd</span> ~ && <span class="built_in">mkdir</span> -p .<span class="built_in">local</span>/opt && <span class="built_in">cd</span> .<span class="built_in">local</span>/opt</span><br><span class="line">git <span class="built_in">clone</span> https://aur.archlinux.org/yay-bin.git</span><br><span class="line"><span class="built_in">cd</span> yay-bin && makepkg -si</span><br></pre></td></tr></table></figure>
<h3 id="安装中文字体"><a href="#安装中文字体" class="headerlink" title="安装中文字体"></a>安装中文字体</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo pacman -S noto-fonts noto-fonts-cjk noto-fonts-emoji</span><br><span class="line"><span class="built_in">cd</span> ~ && <span class="built_in">mkdir</span> -p .config/fontconfig</span><br><span class="line">kate .config/fontconfig/fonts.conf</span><br></pre></td></tr></table></figure>
<p>配置如下:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version='1.0' encoding='UTF-8'?></span></span><br><span class="line"><span class="meta"><!DOCTYPE <span class="keyword">fontconfig</span> <span class="keyword">SYSTEM</span> <span class="string">'urn:fontconfig:fonts.dtd'</span>></span></span><br><span class="line"><span class="comment"><!-- ${XDG_CONFIG_HOME}/fontconfig/fonts.conf</span></span><br><span class="line"><span class="comment"> - vim:ft=xml:fenc=utf-8:noet:ts=3:sw=3:</span></span><br><span class="line"><span class="comment"> --></span></span><br><span class="line"><span class="tag"><<span class="name">fontconfig</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>serif<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Serif<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Color Emoji<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK SC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK TC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK JP<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>sans-serif<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Color Emoji<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK SC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK TC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans CJK JP<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>monospace<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans Mono<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Color Emoji<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans Mono CJK SC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans Mono CJK TC<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">family</span>></span>Noto Sans Mono CJK JP<span class="tag"></<span class="name">family</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">prefer</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">alias</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">match</span> <span class="attr">target</span>=<span class="string">"font"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"antialias"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bool</span>></span>true<span class="tag"></<span class="name">bool</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"autohint"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bool</span>></span>true<span class="tag"></<span class="name">bool</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"dpi"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">double</span>></span>96<span class="tag"></<span class="name">double</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"hinting"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bool</span>></span>true<span class="tag"></<span class="name">bool</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"hintstyle"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">const</span>></span>hintslight<span class="tag"></<span class="name">const</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"lcdfilter"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">const</span>></span>lcdlight<span class="tag"></<span class="name">const</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"rgba"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">const</span>></span>rgb<span class="tag"></<span class="name">const</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">edit</span> <span class="attr">mode</span>=<span class="string">"assign"</span> <span class="attr">name</span>=<span class="string">"size"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">int</span>></span>15<span class="tag"></<span class="name">int</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">edit</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">match</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dir</span>></span>~/.fonts<span class="tag"></<span class="name">dir</span>></span></span><br><span class="line"><span class="tag"></<span class="name">fontconfig</span>></span></span><br></pre></td></tr></table></figure>
<p>刷新字体缓存:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">fc-cache -fv</span><br></pre></td></tr></table></figure>
<p>可能需要重启生效,但是先不重启。</p>
<h3 id="安装常用软件"><a href="#安装常用软件" class="headerlink" title="安装常用软件"></a>安装常用软件</h3><p>谜之 CPU 软件包,不要全装:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo pacman -S amd-ucode</span><br><span class="line">sudo pacman -S intel-ucode</span><br></pre></td></tr></table></figure>
<p>man,浏览器,输入法,vscode,openssh,firacode,flameshot:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yay -S man-db man-pages google-chrome-stable fcitx5-im fcitx5-qt fcitx5-gtk fcitx5-chinese-addons fcitx5-pinyin-zhwiki visual-studio-code-bin openssh ttf-fira-code nerd-fonts-fira-code flameshot</span><br></pre></td></tr></table></figure>
<p>在 System Settings 添加 Pinyin 输入。修改切换输入法的快捷键。</p>
<p>如果需要开启启动 sshd:<code>systemctl enable --now sshd</code></p>
<p>网络自由,Fuck Timeout:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -Ls https://mirrors.v2raya.org/go.sh | sudo bash</span><br><span class="line">sudo systemctl <span class="built_in">disable</span> v2ray --now</span><br><span class="line">yay -S v2raya-bin</span><br><span class="line">sudo systemctl start v2raya.service</span><br><span class="line">sudo systemctl <span class="built_in">enable</span> v2raya.service</span><br></pre></td></tr></table></figure>
<p>进浏览器管理 <code>127.0.0.1:2017</code>。</p>
<h3 id="还原-zsh"><a href="#还原-zsh" class="headerlink" title="还原 zsh"></a>还原 zsh</h3><p>参考 <a href="https://github.com/Codesire-Deng/rc">Codesire-Deng/rc</a></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yay -S zsh</span><br><span class="line">chsh -s $(<span class="built_in">which</span> zsh)</span><br><span class="line">sh -c <span class="string">"<span class="subst">$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>"</span></span><br><span class="line">yay -S zsh-syntax-highlighting-git</span><br></pre></td></tr></table></figure>
<ul>
<li>覆盖拷贝 <a href="https://github.com/Codesire-Deng/rc">Codesire-Deng/rc</a> 的配置文件。</li>
<li>安装完后,调整 konsole 和 vscode 的默认终端。</li>
</ul>
<h3 id="开发环境"><a href="#开发环境" class="headerlink" title="开发环境"></a>开发环境</h3><h4 id="C-相关"><a href="#C-相关" class="headerlink" title="C++ 相关"></a>C++ 相关</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yay -S linux-headers cmake clang mimalloc boost cloc </span><br></pre></td></tr></table></figure>
<h4 id="perf-相关"><a href="#perf-相关" class="headerlink" title="perf 相关"></a>perf 相关</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yay -S perf</span><br><span class="line"><span class="built_in">cd</span> ~/Downloads && git <span class="built_in">clone</span> https://github.com/brendangregg/FlameGraph.git</span><br><span class="line">sudo <span class="built_in">ln</span> -s ~/Downloads/FlameGraph/stackcollapse-perf.pl /usr/bin/stackcollapse-perf.pl</span><br><span class="line">sudo <span class="built_in">ln</span> -s ~/Downloads/FlameGraph/flamegraph.pl /usr/bin/flamegraph.pl</span><br></pre></td></tr></table></figure>
<h4 id="Node-相关"><a href="#Node-相关" class="headerlink" title="Node 相关"></a>Node 相关</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n</span><br><span class="line">sudo bash n lts</span><br><span class="line">sudo npm install -g n</span><br><span class="line"><span class="built_in">rm</span> n</span><br><span class="line">sudo npm install -g cnpm --registry=https://registry.npmmirror.com</span><br><span class="line">sudo npm install -g hexo-cli</span><br></pre></td></tr></table></figure>
<h3 id="编译-Clang-套件"><a href="#编译-Clang-套件" class="headerlink" title="编译 Clang 套件"></a>编译 Clang 套件</h3><blockquote>
<p>若已有 <code>yay clang-git</code> 则可忽略此步。但是 <code>clang-git</code> 的质量堪忧。</p>
</blockquote>
<p>参考 <a href="https://clang.llvm.org/get_started.html">Clang Docs</a></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/llvm/llvm-project.git</span><br><span class="line"><span class="built_in">cd</span> llvm-project</span><br><span class="line">cmake -B build -DLLVM_ENABLE_PROJECTS=<span class="string">"clang;clang-tools-extra;compiler-rt"</span> -DCMAKE_BUILD_TYPE=Release -G <span class="string">"Unix Makefiles"</span> ./llvm</span><br><span class="line">cmake --build build -j 14 <span class="comment"># 不要使用全部的核心!UI会崩溃</span></span><br><span class="line">cmake --install build</span><br><span class="line"><span class="built_in">which</span> clang-format</span><br><span class="line">clang-format --version</span><br></pre></td></tr></table></figure>
<p>可在 llvm-project/llvm/CMakeLists.txt 中搜索 <code>LLVM_ALL_PROJECTS</code> 查看所有项目。</p>
<h3 id="生成-SSH-key"><a href="#生成-SSH-key" class="headerlink" title="生成 SSH key"></a>生成 SSH key</h3><p>参考 <a href="https://docs.github.com/cn/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">Github Docs</a></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ssh-keygen -t ed25519 -C <span class="string">"[email protected]"</span></span><br><span class="line"><span class="built_in">eval</span> <span class="string">"<span class="subst">$(ssh-agent -s)</span>"</span></span><br><span class="line">ssh-add ~/.ssh/id_ed25519</span><br></pre></td></tr></table></figure>
<p>将公钥添加到 <a href="https://github.com/settings/keys">Github SSH keys</a>。</p>
<h3 id="systemd-boot-追加-Windows"><a href="#systemd-boot-追加-Windows" class="headerlink" title="systemd-boot 追加 Windows"></a>systemd-boot 追加 Windows</h3><ol>
<li>找到 Windows EFI 分区:<ul>
<li><code>lsblk -o NAME,FSTYPE,SIZE,MOUNTPOINT</code></li>
</ul>
</li>
<li>挂载这个分区:<ul>
<li><code>sudo mkdir /mnt/win-efi</code></li>
<li><code>sudo mount /dev/sdb1xxx /mnt/win-efi</code></li>
</ul>
</li>
<li>拷贝 Windows EFI 到当前硬盘的 EFI(<code>/boot/EFI</code>在不同系统下可能不同,例如可能是<code>/boot/efi/EFI</code>)<ul>
<li><code>sudo cp -r /mnt/win-efi/EFI/Microsoft /boot/EFI</code></li>
</ul>
</li>
<li>修改 bootloader 的配置,例如倒计时<ul>
<li>参考 <a href="https://wiki.archlinux.org/title/Systemd-boot">Wiki: Systemd-boot</a></li>
<li><code>kate /boot/loader/loader.conf</code></li>
</ul>
</li>
<li>重启即可。在选择界面按 <code>d</code> 即可指定默认项。</li>
</ol>
<h3 id="开机挂载硬盘"><a href="#开机挂载硬盘" class="headerlink" title="开机挂载硬盘"></a>开机挂载硬盘</h3><ol>
<li>确认设备文件名<ul>
<li><code>sudo fdisk -l</code></li>
</ul>
</li>
<li>找到 UUID 和文件系统类型<ul>
<li><code>blkid</code></li>
</ul>
</li>
<li>条目追加至 <code>/etc/fstab</code><ul>
<li>例如:<code>UUID=3A848F25848EE32D /run/media/codesire/3A848F25848EE32D ntfs defaults 0 0</code></li>
</ul>
</li>
</ol>
<h3 id="附:Windows-改为-UTC-时间"><a href="#附:Windows-改为-UTC-时间" class="headerlink" title="附:Windows 改为 UTC 时间"></a>附:Windows 改为 UTC 时间</h3><p>新建<code>UTC.reg</code>,内容如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version 5.00</span><br><span class="line"></span><br><span class="line">[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]</span><br><span class="line">"RealTimeIsUniversal"=dword:00000001</span><br></pre></td></tr></table></figure>
<p>用管理员运行之即可。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>珍爱生命,珍惜时间。</p>
]]></content>
<tags>
<tag>减少脱发指南</tag>
</tags>
</entry>
<entry>
<title>C++20 Coroutine</title>
<url>/2021/11/18/C-20-Coroutine/</url>
<content><![CDATA[<blockquote>
<p><strong>协程</strong>(coroutine)是能够暂停和恢复的函数。</p>
</blockquote>
<p>协程是<strong>线程阻塞造成性能下降</strong>的最佳解决方案,尤其是应用在静态线程池中。</p>
<span id="more"></span>
<small>
<h2 id="吐槽"><a href="#吐槽" class="headerlink" title="吐槽"></a><del>吐槽</del></h2><p><del>GoLang 设计的 goroutine 简单好用,大名鼎鼎;虚拟机语言(例如 C#,Javascript,Java)的协程更是逆天改命,强势占领高性能并发的高地;机器队这边简直神速,而我们的人工队 C++ 在干什么呢,不会还没做完 STL network 吧,不会连 <code>format</code> 的编译器支持都没有吧,不会还没推广 <code>import <module></code> 吧 🥵🥵🥵……</del></p>
<h2 id="C-20-协程永远的神"><a href="#C-20-协程永远的神" class="headerlink" title="C++20 协程永远的神"></a>C++20 协程永远的神</h2></small>
<h2 id="C-的协程"><a href="#C-的协程" class="headerlink" title="C++的协程"></a>C++的协程</h2><p>C++的协程是:</p>
<ol>
<li>对称的。一个协程暂停后,可返回 caller 或恢复任意协程。</li>
<li>语言级特性。编译器知道你在使用协程。然而不比库强到哪里去。</li>
<li>无栈(Stackless) 的。没有独立运行时栈,无惧爆栈,调度成本低。</li>
</ol>
<p>一个协程在被命令「暂停」时,会保证将数据和当前运行位置保存在堆内存(以便恢复现场),然后转移运行权。</p>
<p>协程允许程序员更美观地编写异步代码,也使懒惰求值的算法成为可能。</p>
<p>当一个函数出现以下三种关键字之一,它就是协程:</p>
<ol>
<li><code>co_await</code> 暂停(直到被命令「恢复」)。</li>
<li><code>co_yield</code> 暂停同时返回一个值。</li>
<li><code>co_return</code> 结束整个协程并返回一个值。</li>
</ol>
<h2 id="使用协程的理由"><a href="#使用协程的理由" class="headerlink" title="使用协程的理由"></a>使用协程的理由</h2><ol>
<li>相比于回调和 sender/receiver,协程的使用<strong>成本更低</strong>,<strong>性能下限更高</strong>。</li>
<li>降低使用者的心智负担和阅历要求,催化高质量工程,资本宠儿。<ul>
<li>例如可以摆脱 asio 里常见的 <code>std::shared_ptr</code>。</li>
</ul>
</li>
</ol>
<h2 id="C-协程的弱点"><a href="#C-协程的弱点" class="headerlink" title="C++协程的弱点"></a>C++协程的弱点</h2><ol>
<li>除非编译器优化,每个协程都需要通过 <code>operator new</code> 来分配 frame:<ul>
<li>动态内存分配可能引发性能问题;</li>
<li>在嵌入式或异构(例如 GPU)环境下,缺乏动态内存分配能力,难以工作。</li>
</ul>
</li>
<li>除非编译器优化,协程的可定制点太多,需要大量间接调用/跳转(而不是内联),同样引起性能问题。<ul>
<li>目前,编译器通常难以内联协程;</li>
<li>HALO 优化理论:<a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2300r3.html#biblio-p0981r0">P0981R0</a>。</li>
</ul>
</li>
<li><strong>动态分配</strong>和<strong>间接调用</strong>的存在,导致协程暂时无法成为异步框架的最优方法。</li>
<li>Debug 的体验风评不佳。</li>
</ol>
<h2 id="协程的限制"><a href="#协程的限制" class="headerlink" title="协程的限制"></a>协程的限制</h2><ol>
<li>不能使用可变参数(variadic arguments),但可以使用变参模板</li>
<li>不能使用 <code>return</code></li>
<li>不能使用占位符返回类型 (<code>auto</code> 或者 Concept)</li>
<li>不能是 constexpr 函数</li>
<li>不能是构造函数或者析构函数</li>
<li>不能是 <code>main</code> 函数</li>
</ol>
<h2 id="协程的运行过程"><a href="#协程的运行过程" class="headerlink" title="协程的运行过程"></a>协程的运行过程</h2><p>所有协程必须关联着几个对象:</p>
<ol>
<li><strong>promise object</strong>,在协程内部进行操作,协程向其写入结果或者异常。</li>
<li><strong>coroutine handle</strong>,在协程外部进行操作,用于恢复协程或者销毁协程帧(frame)。</li>
<li><strong>coroutine state</strong>,保存协程的信息,分配于堆内存上(除非被优化),对程序员不可见。具体保存着:<ol>
<li>promise object</li>
<li>所有协程参数(按值复制或移动)</li>
<li>记录暂停点的状态机</li>
<li>局部变量和临时变量</li>
</ol>
</li>
</ol>
<hr>
<p>当协程「开始」时,它会:</p>
<ol>
<li>使用 <code>operater new</code> 来构造 coroutine state。</li>
<li>将所有协程参数拷贝或移动到 coroutine state。<em>小心发生「垂悬引用」,尤其在协程恢复后。</em></li>
<li>构造 promise object。优先调用接受所有协程参数的构造函数,否则调用默认构造函数。</li>
<li>调用 <code>promise.get_return_object()</code>,其结果保存为本地变量。此结果在第一次暂停时会返回给 caller。包括这一步和以前的步骤,所有异常都会抛给 caller,而不是放入 promise object。</li>
<li>调用 <code>promise.initial_suspend()</code>,紧接着 <code>co_await</code> 它。<em>常见的返回值是 <code>suspend_always</code> 用于懒汉协程,或者 <code>suspend_never</code> 用于饿汉协程</em></li>
<li>当 <code>co_await promise.initial_suspend()</code> 恢复后,协程开始运行其函数体。</li>
</ol>
<hr>
<p>当协程到达暂停点,它会:</p>
<ol>
<li>将 return object 返回给执行权所有者,类型应为协程的返回类型,允许发生隐式转换。</li>
</ol>
<hr>
<p>当协程到达 <code>co_return [expr]</code> 语句,它会:</p>
<ol>
<li>调用 <code>promise.return_void()</code>,条件是:<ol>
<li><code>co_return;</code></li>
<li><code>co_return expr</code> 而 expr 的类型是 void</li>
<li>函数体结束</li>
</ol>
</li>
<li>或者调用 <code>promise.return_value(expr)</code>。</li>
<li>析构所有自动变量。</li>
<li>调用 <code>promise.final_suspend()</code>,紧接着 <code>co_await</code> 它。</li>
</ol>
<hr>
<p>当协程因未捕获异常而结束,它会:</p>
<ol>
<li>捕获这个异常,并在 <code>catch</code> 块中调用 <code>promise.unhandled_exception()</code></li>
<li>调用 <code>promise.final_suspend()</code>,紧接着 <code>co_await</code> 它。此时恢复另一个协程是 UB。</li>
</ol>
<hr>
<p>当 coroutine state 被析构(要么遇到 <code>co_return</code>,要么未捕获异常,在要么被 handle 销毁)时,它会:</p>
<ol>
<li>析构 promise object。</li>
<li>析构所有协程参数。</li>
<li>使用 <code>operator delete</code> 来释放coroutine state。</li>
<li>转移执行权。</li>
</ol>
<h2 id="关于堆分配"><a href="#关于堆分配" class="headerlink" title="关于堆分配"></a>关于堆分配</h2><p>动态内存分配可能成为<strong>严重性能瓶颈</strong>!</p>
<p>程序员可通过自定义 <code>operator new</code> 来控制 coroutine state 的分配。这部分暂时忽略不讲。</p>
<h2 id="Promise类型推导"><a href="#Promise类型推导" class="headerlink" title="Promise类型推导"></a>Promise类型推导</h2><p>这部分暂时忽略不讲。</p>
<h2 id="co-await"><a href="#co-await" class="headerlink" title="co_await"></a>co_await</h2><p>一元运算符 <code>co_await</code> 会暂停协程并将转移执行权。其操作数必须定义 <code>operator co_await</code>,或者能通过当前协程的 <code>Promise::await_transform</code> 转换成这种类型。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">co_await</span> expr</span><br></pre></td></tr></table></figure>
<p><img src="/2021/11/18/C-20-Coroutine/co_await.svg" alt="co_await"></p>
<p>首先,<code>expr</code> 要被转换为 awaitable,规则如下:</p>
<ol>
<li>如果 <code>expr</code> 是由 initial suspend point,final suspend point 或者 yield expression 生成的,那么 awaitable 就是 <code>expr</code> 本身。</li>
<li>否则,如果当前协程有定义 <code>Promise::await_transform</code>,那么 awaitable 就是 <code>promise.await_transform(expr)</code>。</li>
<li>否则,awaitable 就是 <code>expr</code> 本身。</li>
</ol>
<p>然后生成一个 awaiter object,规则如下:</p>
<ol>
<li>根据重载解析的结果调用 <code>opeartor co_await(awaitable)</code> 或者 <code>awaitable.operator co_await()</code></li>
<li>如果重载解析找不到函数,那么 awaiter 就是 awaitable 本身。</li>
<li>如果重载解析有歧义,那么程序是 ill-formed。</li>
</ol>
<p>然后,调用 <code>awaiter.await_ready()</code>并判断,决定是否暂停协程:</p>
<ul>
<li>若 <code>false</code>,协程暂停,必要的信息存放于 coroutine state。然后调用 <code>awaiter.await_suspend(handle)</code>。在这个函数中,通过 <code>handle</code> 可以访问 coroutine state,也是这个函数有责任安排协程在某个 executor 上恢复(甚至立即就地恢复),或者干脆销毁协程:<ul>
<li>若 <code>awaiter.await_suspend(handle)</code> 返回 void,执行权立即转移(给 caller/resumer)。</li>
<li>否则若返回 bool,<ul>
<li>若 <code>true</code> 则转移执行权(给 caller/resumer)。</li>
<li>若 <code>false</code> 则恢复当前协程。</li>
</ul>
</li>
<li>否则返回一个 coroutine handle(对应其他协程),调用这个 <code>other_handle.resume()</code>。注意链式调用可能最终恢复当前协程。</li>
<li>若 <code>awaiter.await_suspend()</code> 抛出一个异常,则立即恢复协程,并由该协程接收这个异常。</li>
<li>当前协程恢复后,返回 <code>awaiter.await_resume()</code> 作为 <code>co_await expr</code> 的结果。</li>
</ul>
</li>
<li>若 <code>true</code>,协程直接返回 <code>awaiter.await_resume()</code> 作为 <code>co_await expr</code> 的结果。</li>
</ul>
<p>注意在进入 <code>awaiter.await_suspend(handle)</code> 之前,当前协程已经完全暂停,此时当前 handle 可以在线程之间自由传递,并由其他调度者恢复。在这种情况下,协程可能已经被恢复,awaiter 随之已经被析构,所以 <code>await_suspend()</code> 不应再访问 <code>*this</code>。</p>
<h2 id="co-yield"><a href="#co-yield" class="headerlink" title="co_yield"></a>co_yield</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">co_yield</span> expr</span><br><span class="line"><span class="keyword">co_yield</span> braced-init-list</span><br></pre></td></tr></table></figure>
<p>等价于</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">co_await</span> promise.<span class="built_in">yield_value</span>(expr)</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>C++</tag>
<tag>Concurrency</tag>
<tag>语法</tag>
<tag>C++协程</tag>
</tags>
</entry>
<entry>
<title>C++ Smart Pointer</title>
<url>/2021/12/15/C-smart-pointer/</url>
<content><![CDATA[<p>本文意在阐述 C++ 智能指针的实现原理,应用场景和潜在的坑。</p>
<span id="more"></span>
<h2 id="shared-ptr"><a href="#shared-ptr" class="headerlink" title="shared_ptr"></a>shared_ptr</h2><p>基于引用计数的智能指针。在构造和析构时修改引用计数。当引用计数为 0 时,析构资源。</p>
<p>单个引用计数器是线程安全的。但是,shared_ptr 并非线程安全,至少因为两个原因:</p>
<ol>
<li>「改写资源指针」与「改写引用计数」,这两步并非原子化。</li>
<li>「改写共享引用计数」与「改写弱引用计数」,这两步也非原子化。</li>
</ol>
<p>陈硕的书和<a href="https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html">博客</a>给出了一个竞争条件的例子。如果要从多个线程读写同一个 shared_ptr 对象,是<strong>需要加锁</strong>的。</p>
<h3 id="shared-ptr-的实现原理"><a href="#shared-ptr-的实现原理" class="headerlink" title="shared_ptr 的实现原理"></a>shared_ptr 的实现原理</h3><ol>
<li>一个 <code>shared_ptr</code> 持有两个裸指针,分别指向资源和「控制块」 (control block)</li>
<li>控制块主要用于实现引用计数。具体的内容有:<ol>
<li>指向所管理资源的指针,或者是资源本身</li>
<li>deleter(已擦除类型)</li>
<li>allocator(已擦除类型)</li>
<li><code>shared_ptr</code> 共享引用计数器</li>
<li><code>weak_ptr</code> 弱引用计数器 (主流实现中 <code>shared_ptr</code> 亦计入弱引用计数)</li>
</ol>
</li>
<li>若使用 <code>make_shared</code> 或 <code>allocate_shared</code> 来构造指针,则资源和控制块一同参与内存申请。<em>这是潜在坑点。</em></li>
<li>若弱引用计数器不清零,则控制块占用的内存不会被回收。因此若控制块很大(直接存放了资源),则空间性能可能变差。</li>
</ol>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 别名构造函数(aliasing constructor)</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>< <span class="keyword">class</span> Y ></span></span><br><span class="line"><span class="function"><span class="title">shared_ptr</span><span class="params">( <span class="type">const</span> shared_ptr<Y>& r, element_type* ptr )</span> <span class="keyword">noexcept</span></span>;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">新智能指针共享与 r 的控制块,但是 this->get() 总是返回 ptr,这可以与 r.get() 不相同!</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">场景:ptr 是 *r 的数据成员,若 r 存活,则 ptr 必定存活。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>
<h3 id="shared-ptr-的潜在坑点"><a href="#shared-ptr-的潜在坑点" class="headerlink" title="shared_ptr 的潜在坑点"></a>shared_ptr 的潜在坑点</h3><ol>
<li>环形引用导致内存泄漏:使用一个 <code>weak_ptr</code> 来打破环形引用。</li>
<li>不支持 <code>shared_ptr<T[]></code>:使用 <code>vector</code> 或 <code>array</code> 作容器。</li>
<li>容许基类析构函数不是虚函数(似乎是因为构造是泛型的):小心。</li>
<li>退化成链状的数据结构在析构时<strong>栈溢出</strong>:反思数据结构的退化,以及是否滥用了 <code>shared_ptr</code>。</li>
<li>重复使用一个裸指针构造两个 <code>shared_ptr</code>,导致双重释放:<ol>
<li>裸指针用完就扔。</li>
<li>不要使用 <code>this</code> 来构造智能指针,而是使用 <code>shared_from_this</code>。<em>这是潜在坑点。</em></li>
</ol>
</li>
<li>若没有任何 <code>shared_ptr</code> 持有对象,此时调用 <code>shared_from_this</code> 就会出问题:<ol>
<li>C++17 之前,这是 UB。</li>
<li>C++17 以后,程序抛出 <code>std::bad_weak_ptr</code> 异常。</li>
<li>最佳实践是禁止访问构造函数,而提供工厂函数返回智能指针。</li>
</ol>
</li>
<li>将 <code>shared_ptr</code> 复制给未知的函数,可能导致环形引用。<ul>
<li>好比不要在上锁后执行为未知用户代码</li>
<li>GC 语言同样有回调地狱导致内存泄漏的问题</li>
</ul>
</li>
</ol>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// enable_shared_from_this 最佳实践</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Best</span> : std::enable_shared_from_this<Best> <span class="comment">// 公有继承</span></span><br><span class="line">{</span><br><span class="line"> <span class="function">std::shared_ptr<Best> <span class="title">getptr</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">shared_from_this</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 工厂函数返回智能指针</span></span><br><span class="line"> <span class="comment">// nodiscard 防止暴毙</span></span><br><span class="line"> [[nodiscard]] <span class="function"><span class="type">static</span> std::shared_ptr<Best> <span class="title">create</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 不能使用 make_shared 因为构造函数是私有的,不能转发</span></span><br><span class="line"> <span class="keyword">return</span> std::<span class="built_in">shared_ptr</span><Best>(<span class="keyword">new</span> <span class="built_in">Best</span>());</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="built_in">Best</span>() = <span class="keyword">default</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h3 id="shared-ptr-的应用场景-x2F-条件"><a href="#shared-ptr-的应用场景-x2F-条件" class="headerlink" title="shared_ptr 的应用场景/条件"></a>shared_ptr 的应用场景/条件</h3><ol>
<li>多线程/异步协作,难以确定资源生命周期。</li>
<li>主从关系明确,不存在环形引用:树、链表、DAG</li>
<li>可以在构造函数参数中提供自定义析构器:<code>[](Y* rc) { delect rc; }</code>。</li>
<li>原本使用 <code>unique_ptr</code> 的数据结构,需要对外提供强引用,则可以改用 <code>shared_ptr</code>。</li>
</ol>
<h2 id="unique-ptr"><a href="#unique-ptr" class="headerlink" title="unique_ptr"></a>unique_ptr</h2><p>独占所有权的智能指针。当持有资源且退出作用域时,会析构资源。所有权只能移动或引用,不能拷贝。</p>
<h3 id="unique-ptr-的潜在坑点"><a href="#unique-ptr-的潜在坑点" class="headerlink" title="unique_ptr 的潜在坑点"></a>unique_ptr 的潜在坑点</h3><ol>
<li>退化成链的数据结构在析构时<strong>栈溢出</strong>:反思数据结构;重写数据结构的析构函数。</li>
</ol>
<h3 id="unique-ptr-的应用场景"><a href="#unique-ptr-的应用场景" class="headerlink" title="unique_ptr 的应用场景"></a>unique_ptr 的应用场景</h3><ol>
<li>提供动态对象的 RAII,保证发生异常时对象仍被析构。</li>
<li>表示对堆对象的独占关系。适合用作类的数据成员,例如表示树的儿子。<ul>
<li>父亲指针永远只用裸指针,因为儿子存活时父亲一定存活</li>
<li>单/双向链表是树的特例,也适用 <code>unique_ptr</code> + 裸指针。</li>
</ul>
</li>
<li>在接口中明确表达「传递所有权」的语义。</li>
<li>作为具有移动语义的单个容器使用。</li>
<li>管理运行时确定长度的定长数组(<code>unique_ptr<T[]></code> 正确使用 <code>new []</code> <code>delete []</code>)</li>
<li>用于 Pimpl 编译隔离</li>
</ol>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Pimpl 抽象</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">using</span> Pimpl = <span class="type">const</span> unique_ptr<T>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Impl</span>; <span class="comment">// 定义于 *.cpp</span></span><br><span class="line"> Pimpl<Impl> pimpl;</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">// 运行时定长数组</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="type">const</span> unique_ptr<Data[]> array;</span><br><span class="line"> <span class="type">size_t</span> arr_size;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">MyClass</span>(<span class="type">size_t</span> size): <span class="built_in">arr_size</span>(<span class="built_in">make_unique</span><Data[]>(size)) {}</span><br><span class="line"> <span class="comment">/*...*/</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<h2 id="weak-ptr"><a href="#weak-ptr" class="headerlink" title="weak_ptr"></a>weak_ptr</h2><p>从 <code>shared_ptr</code> 构造而来,但不参与引用计数。</p>
<h3 id="weak-ptr-的潜在坑点"><a href="#weak-ptr-的潜在坑点" class="headerlink" title="weak_ptr 的潜在坑点"></a>weak_ptr 的潜在坑点</h3><p>良好的设计使得 <code>weak_ptr</code> 坑点较少,只需记得弱引用的存在会使得控制块无法释放即可。</p>
<h3 id="weak-ptr-的应用场景"><a href="#weak-ptr-的应用场景" class="headerlink" title="weak_ptr 的应用场景"></a>weak_ptr 的应用场景</h3><ol>
<li>打破 <code>shared_ptr</code> 的循环引用。常用于环形链表、回调函数。</li>
<li>作为「可空资源的观察者」。根据资源是否为空表现不同的行为。</li>
</ol>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// weak_ptr 做回调</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bad</span><span class="params">(<span class="type">const</span> shared_ptr<X>& x)</span> </span>{</span><br><span class="line"> obj.<span class="built_in">on_draw</span>([=]{ x-><span class="built_in">extra_work</span>(); }); <span class="comment">// x 已经泄露</span></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">good</span><span class="params">(<span class="type">const</span> shared_ptr<X>& x)</span> </span>{</span><br><span class="line"> obj.<span class="built_in">on_draw</span>([w=<span class="built_in">weak_ptr</span><X>(x)] {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">auto</span> x = w.<span class="built_in">lock</span>()) x-><span class="built_in">extra_work</span>(); <span class="comment">// x 有机会析构</span></span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// weak_ptr 做 cache</span></span><br><span class="line"><span class="function">shared_ptr<Payload> <span class="title">create</span><span class="params">(<span class="type">int</span> id)</span> </span>{</span><br><span class="line"> <span class="type">static</span> map<<span class="type">int</span>, weak_ptr<Payload>> cache;</span><br><span class="line"> <span class="type">static</span> mutex mut_cache;</span><br><span class="line"> <span class="function">lock_guard<mutex> <span class="title">hold</span><span class="params">(mut_cache)</span></span>;</span><br><span class="line"> <span class="keyword">auto</span> sp = cache[id].<span class="built_in">lock</span>();</span><br><span class="line"> <span class="keyword">if</span> (!sp) cache[id] = sp = <span class="built_in">make_shared</span><Payload>();</span><br><span class="line"> <span class="keyword">return</span> sp;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol>
<li><a href="https://en.cppreference.com/w/cpp/memory">cppreference</a></li>
<li><a href="https://www.youtube.com/watch?v=JfmTagWcqoE">CppCon 2016: Herb Sutter “Leak-Freedom in C++... By Default.”</a></li>
<li><a href="http://senlinzhan.github.io/2015/04/24/%E6%B7%B1%E5%85%A5shared-ptr/">Senlin《谈谈 shared_ptr 的那些坑》</a></li>
</ol>
]]></content>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>C++ 多态</title>
<url>/2022/01/28/C-%E5%A4%9A%E6%80%81/</url>
<content><![CDATA[<h1 id="Preface"><a href="#Preface" class="headerlink" title="Preface"></a>Preface</h1><p>「多态」是一个很宽泛的理念,对应繁多实现。本文将记录我所看到的多态的各种形态。作者见识短浅,还请读者多多指教。</p>
<ul>
<li>本文已计划的内容:<ul>
<li><input checked="" disabled="" type="checkbox"> 基于 <code>std::visit</code><ul>
<li><input checked="" disabled="" type="checkbox"> 实现运行时多态的优化</li>
<li><input checked="" disabled="" type="checkbox"> 实现<em>重载模式</em></li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> CRTP 实现静态多态<ul>
<li><input checked="" disabled="" type="checkbox"> 静态多态</li>
<li><input checked="" disabled="" type="checkbox"> 静态接口</li>
<li><input checked="" disabled="" type="checkbox"> CRTP 的一些分析和 trick</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> C++20 前基于 SFINAE 的模板偏特化</li>
<li><input disabled="" type="checkbox"> C++20 后基于 <code>concepts</code> 的模板特化</li>
<li><input disabled="" type="checkbox"> Hack 虚函数表,用于优化<span id="more"></span></li>
</ul>
</li>
<li>本文<strong>不会</strong>出现的内容:<ul>
<li><code>virtual</code> 虚函数的基础用法</li>
<li>函数指针模拟虚函数</li>
<li>运行时反射</li>
</ul>
</li>
</ul>
<h1 id="std-visit"><a href="#std-visit" class="headerlink" title="std::visit"></a>std::visit</h1><h2 id="Runtime-Branching"><a href="#Runtime-Branching" class="headerlink" title="Runtime Branching"></a>Runtime Branching</h2><p>假如你要对一个矢量做线性变换 $\vec{x} = k \cdot \vec{x} + b$,又想针对 $k=1$ 或 $b=0$ 的情况做优化,应该怎么办?</p>
<p>这是最简单却很慢的代码:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<n; ++i) {</span><br><span class="line"> <span class="keyword">if</span> (k != <span class="number">1</span>)</span><br><span class="line"> x[i] *= k;</span><br><span class="line"> <span class="keyword">if</span> (b != <span class="number">0</span>)</span><br><span class="line"> x[i] += b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码满足速度要求,但是太啰嗦:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (k == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">if</span> (b == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (b == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>想两全其美怎么办?可以利用 <code>variant</code> 搭配 <code>visit</code> 使用:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// since C++17</span></span><br><span class="line"><span class="keyword">using</span> bool_variant = std::variant<std::true_type, std::false_type>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> bool_variant <span class="title">to_variant</span><span class="params">(<span class="type">bool</span> x)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (x)</span><br><span class="line"> <span class="keyword">return</span> std::true_type{};</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="keyword">return</span> std::false_type{};</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">linear</span><span class="params">(std::vector<<span class="type">int</span>> &x, <span class="type">int</span> k, <span class="type">int</span> b)</span> </span>{</span><br><span class="line"> bool_variant has_k = <span class="built_in">to_variant</span>(k != <span class="number">1</span>);</span><br><span class="line"> bool_variant has_b = <span class="built_in">to_variant</span>(b != <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">auto</span> transform = [=, &x](<span class="keyword">auto</span> has_k, <span class="keyword">auto</span> has_b) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> &xi : x) {</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">constexpr</span> (has_k)</span><br><span class="line"> xi *= k;</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(has_b)</span></span></span><br><span class="line"><span class="function"> xi +</span>= b;</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> std::<span class="built_in">visit</span>(transform, has_k, has_b);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>此时编译器为 <code>transform</code> 生成四个特化版本,正是我们想要的东西。<code>std::visit()</code> 会在运行时判断 <code>variant</code> 内储存的是哪个 type,然后挑选正确的重载分支。</p>
<p><code>variant</code> + <code>constexpr if</code> 并不是唯一的玩法,我们来看看 <a href="https://en.cppreference.com/w/cpp/utility/variant/visit">cppreference</a> 还有什么花活。</p>
<h2 id="The-Overload-Pattern"><a href="#The-Overload-Pattern" class="headerlink" title="The Overload Pattern"></a>The <em>Overload Pattern</em></h2><p>假设你要 log 一个 <code>variant<int, float, string></code>,你刚刚学到了 <code>constexpr if</code>,于是你想到这一段代码:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">class</span>> <span class="keyword">inline</span> <span class="keyword">constexpr</span> <span class="type">bool</span> always_false_v = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> ifs_variant = std::variant<<span class="type">int</span>, <span class="type">float</span>, std::string>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(<span class="type">const</span> ifs_variant &arg)</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> logger = [](<span class="type">const</span> <span class="keyword">auto</span> &arg) {</span><br><span class="line"> <span class="keyword">using</span> T = std::<span class="type">decay_t</span><<span class="keyword">decltype</span>(arg)>;</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(std::is_same_v<T, <span class="type">int</span>>)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"int: "</span> << arg << <span class="string">"\n"</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> <span class="built_in">constexpr</span> (std::is_same_v<T, <span class="type">float</span>>) {</span><br><span class="line"> std::cout << <span class="string">"float: "</span> << arg << <span class="string">"\n"</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> <span class="built_in">constexpr</span> (std::is_same_v<T, std::string>) {</span><br><span class="line"> std::cout << <span class="string">"string: "</span> << arg << <span class="string">"\n"</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">static_assert</span>(always_false_v<T>, <span class="string">"non-exhaustive visitor!"</span>);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> std::<span class="built_in">visit</span>(logger, arg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>三秒之后你觉得这段代码太丑,太多的 <code>if constexpr</code> 和 <code>is_same_v</code> 语句影响阅读,你也不想用 <code>always_false_v<T></code> 这样的孤儿代码。于是你想到了重载 <code>opeartor()</code>:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(<span class="type">const</span> ifs_variant &arg)</span> </span>{</span><br><span class="line"> <span class="keyword">struct</span> {</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">int</span> i)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"int: "</span> << i << <span class="string">"\n"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">float</span> f)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"float: "</span> << f << <span class="string">"\n"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="type">const</span> std::string &s)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"string: "</span> << s << <span class="string">"\n"</span>;</span><br><span class="line"> }</span><br><span class="line"> } logger;</span><br><span class="line"> </span><br><span class="line"> std::<span class="built_in">visit</span>(logger, arg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码会在缺少某个重载时报错(模板巨型报错),长度也合理,你的血压下降了一些,但仍未到安全区,因为 <code>void operator()</code> 的重复依然很丑。你想用模板元编程自动化这些函数重载。你的目标是:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span>... Lambdas></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Lambdas... {</span><br><span class="line"> <span class="comment">// Import all function whatever the parameter is.</span></span><br><span class="line"> <span class="function"><span class="keyword">using</span> <span class="title">Lambdas::operator</span><span class="params">()</span>...</span>; </span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>这样当你用 lambda 的类型做模板参数,就能自动导入 lambda 的 <code>opeartor()</code>。问题来了,你怎么传入 lambda 的类型?你根本没有办法用尖括号 <code><></code> 来指定 lambda 类型!而自动类型推导至少需要一个函数调用,你想到了构造函数!可以在构造函数中传入 lambda,这样就能推导出 <code>Lambdas</code> 的具体类型!</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span>... Lambdas></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Lambdas... {</span><br><span class="line"> <span class="function"><span class="keyword">using</span> <span class="title">Lambdas::operator</span><span class="params">()</span>...</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Overloaded</span>(Lambdas&&...) {} <span class="comment">// Bang!</span></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">log</span><span class="params">(<span class="type">const</span> ifs_variant &arg)</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> logger = Overloaded{</span><br><span class="line"> [](<span class="type">int</span> i) { std::cout << <span class="string">"int: "</span> << i << <span class="string">"\n"</span>; },</span><br><span class="line"> [](<span class="type">float</span> f) { std::cout << <span class="string">"float: "</span> << f << <span class="string">"\n"</span>; },</span><br><span class="line"> [](<span class="type">const</span> std::string &s) { std::cout << <span class="string">"string: "</span> << s << <span class="string">"\n"</span>; },</span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> std::<span class="built_in">visit</span>(logger, arg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Bang! 第五行编译报错,编译器说,你企图使用 Lambda 的默认构造函数,但这个函数是 <code>deleted</code>!</p>
<p>想了半天,你顿悟过来,Overloaded 的构造函数一定要显式调用 Lambdas 的构造函数才行,于是你写出:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span>... Lambdas></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Lambdas... {</span><br><span class="line"> <span class="function"><span class="keyword">using</span> <span class="title">Lambdas::operator</span><span class="params">()</span>...</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Overloaded</span>(Lambdas &&...t) : Lambdas{std::forward<Lambdas>(t)}... {};</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>编译终于过了!你还擅长举一反三,联想到 <code>make_tuple()</code> 可以在没有尖括号 <code><></code> 的情况下工作,你可以模仿它,于是你写出:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span>... Lambdas></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Lambdas... {</span><br><span class="line"> <span class="function"><span class="keyword">using</span> <span class="title">Lambdas::operator</span><span class="params">()</span>...</span>;</span><br><span class="line"> <span class="comment">// no more explicit ctor</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span>... Lambdas></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">auto</span> <span class="title">make_Overloaded</span><span class="params">(Lambdas &&...t)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> Overloaded<Lambdas...>{std::forward<Lambdas>(t)...};</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这段代码很秀也很正确。可惜好景不长,一个保洁阿姨路过了你,说,小伙子你写的不行,来看我秀一手:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">class</span>... Ts> <span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Ts... { <span class="function"><span class="keyword">using</span> <span class="title">Ts::operator</span><span class="params">()</span>...</span>; };</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">class</span>... Ts> <span class="title">Overloaded</span><span class="params">(Ts...)</span> -> Overloaded<Ts...></span>;</span><br></pre></td></tr></table></figure>
<p>编译通过!阿姨的第二行模板恰好替代了 <code>make_Overloaded</code>,正确推导出所有模板参数!这是 C++17 的新功能 *<a href="https://en.cppreference.com/w/cpp/language/class_template_argument_deduction">Custom Template Argument Deduction Rules</a>*。这一功能在 C++20 中更加智能,所以你可以删掉那行显式推导规则。</p>
<p>现在,你进化成了 <code>std::visit</code> 不用 <code>Overloaded{}</code> 就不舒服星人。</p>
<h3 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></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"><variant></span> <span class="comment">// C++17</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> ifs_variant = std::variant<<span class="type">int</span>, <span class="type">float</span>, std::string>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">class</span>... Ts> <span class="keyword">struct</span> <span class="title class_">Overloaded</span> : Ts... { <span class="function"><span class="keyword">using</span> <span class="title">Ts::operator</span><span class="params">()</span>...</span>; };</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">class</span>... Ts> <span class="title">Overloaded</span><span class="params">(Ts...)</span> -> Overloaded<Ts...></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">log</span><span class="params">(<span class="type">const</span> ifs_variant &arg)</span> </span>{</span><br><span class="line"> std::<span class="built_in">visit</span>(Overloaded{</span><br><span class="line"> [](<span class="type">int</span> i) { std::cout << <span class="string">"int: "</span> << i << <span class="string">"\n"</span>; },</span><br><span class="line"> [](<span class="type">float</span> f) { std::cout << <span class="string">"float: "</span> << f << <span class="string">"\n"</span>; },</span><br><span class="line"> [](<span class="type">const</span> std::string &s) { std::cout << <span class="string">"string: "</span> << s << <span class="string">"\n"</span>; },</span><br><span class="line"> }, arg);</span><br><span class="line">}</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"> ifs_variant i{<span class="number">10</span>}, <span class="built_in">f</span>(<span class="number">0.7f</span>), s{<span class="string">"string"</span>};</span><br><span class="line"> <span class="built_in">log</span>(i);</span><br><span class="line"> <span class="built_in">log</span>(f);</span><br><span class="line"> <span class="built_in">log</span>(s);</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>
<h1 id="The-CRTP"><a href="#The-CRTP" class="headerlink" title="The CRTP"></a>The <em>CRTP</em></h1><p>Curiously recurring template pattern (CRTP) 是指一个类的父类的模板参数含有自己。例如:</p>
<figure class="highlight cpp"><table><tr><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="keyword">class</span> <span class="title class_">Counter</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="keyword">class</span> <span class="title class_">Foo</span> : Counter<Foo> {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>如此一来,父类在编译期就知道自己的派生类型,可以利用这一点做很多事,比如模板化的对象计数器、编译期多态、非退化的链式调用、模板化接口实现等等。网上很多声音说 CRTP 主要是用来消除动态绑定,但我认为 CRTP 主要是用来约束和简化代码的。我们来看看 CRTP 到底有什么用。</p>
<h2 id="Static-Polymorphism"><a href="#Static-Polymorphism" class="headerlink" title="Static Polymorphism"></a>Static Polymorphism</h2><p>由于父类知道派生类型,我们可以用 <code>static_cast<T*>(this)</code> 一步到位,将自己向下转型。还可以调用 <code>T::static_func</code> 实现静态分发。</p>
<h3 id="Demo-1"><a href="#Demo-1" class="headerlink" title="Demo"></a>Demo</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></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="keyword">struct</span> <span class="title class_">Base</span> {</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">member_func</span><span class="params">()</span> </span>{ <span class="built_in">static_cast</span><T *>(<span class="keyword">this</span>)-><span class="built_in">member_func_impl</span>(); }</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">static_func</span><span class="params">()</span> </span>{ T::<span class="built_in">static_func_impl</span>(); }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Base<Obj> {</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">member_func_impl</span><span class="params">()</span> </span>{ std::cout << <span class="string">"Obj::member_func_impl\n"</span>; }</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">static_func_impl</span><span class="params">()</span> </span>{ std::cout << <span class="string">"Obj::static_func_impl\n"</span>; }</span><br><span class="line">};</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"> Obj a;</span><br><span class="line"> Base<Obj> &ref = a;</span><br><span class="line"> ref.<span class="built_in">member_func</span>();</span><br><span class="line"> Base<Obj>::<span class="built_in">static_func</span>();</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>
<p>会不会觉得有点脱裤子放屁?这样写有什么优势?用了 CRTP 之后,就宣告丧失了动态分发的能力,那为什么不干脆把 <code>Base</code> 类删掉呢?</p>
<p>有人说,<code>Base</code> 类描述了子类应该实现的几个函数,而且没有运行时开销。我认为最好使用 C++20 concept 来实现这种编译期约束。用 CRTP 是可以的,但是请注意标识符命名,我们来看一些 Bugs。</p>
<h3 id="Bugs"><a href="#Bugs" class="headerlink" title="Bugs"></a>Bugs</h3><figure class="highlight cpp"><table><tr><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="keyword">struct</span> <span class="title class_">Base</span> {</span><br><span class="line"> <span class="comment">// Notice the "T::member_func()"</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">member_func</span><span class="params">()</span> </span>{ <span class="built_in">static_cast</span><T *>(<span class="keyword">this</span>)->T::<span class="built_in">member_func</span>(); }</span><br><span class="line"> <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">static_func</span><span class="params">()</span> </span>{ T::<span class="built_in">static_func</span>(); }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Base<Obj> {</span><br><span class="line"> <span class="comment">// empty!</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>编译没有任何报错,而程序在运行时会因为无穷递归而陷入栈溢出。所以千万<strong>不要为了美观,让子类重复基类的函数名</strong>。</p>
<p>我们再看一个人为制造的错误:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Base<Obj2> { <span class="comment">// a bug compiles</span></span><br><span class="line"> <span class="comment">// implementation which is unused</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>编译器不会给你任何提醒,而你,<code>Obj</code>,沦为 <code>Obj2</code> 的替身。所幸的是,这个 bug 是可以修复的!只要将 <code>Base</code> 的构造函数设为私有,再加一个友元即可:</p>
<figure class="highlight cpp"><table><tr><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="keyword">struct</span> <span class="title class_">Base</span> {</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="built_in">Base</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="keyword">friend</span> T;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj2</span> {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Base<Obj2> { <span class="comment">// Instantiating Obj does not compile</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>现在,<code>Obj</code> 无权访问 <code>Base</code> 的构造函数,如果企图构造 <code>Obj</code> 对象,编译器会报错,然而你还是要检查半天才能发现,原来是 CRTP 写错了!更惨的是,如果你只调用静态成员,那么编译通过!天王老子来了都不报错!慢慢找 Bug 吧你就。</p>
<h2 id="Template-Interface-Implementation"><a href="#Template-Interface-Implementation" class="headerlink" title="Template Interface Implementation"></a>Template Interface Implementation</h2><p>目前为止 CRTP 被我批评得太多了,实际上 CRTP 还是有点用的,它可以模板化接口实现,而这一点在 C++20 concept 中我暂时没发现对应的东西。</p>
<p>假设你要设计一个 <code>Scaleable</code> 接口,对于每一个具体类,它的逻辑都是:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Scaleable {</span><br><span class="line"> <span class="comment">// getter, setter ...</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">override</span> <span class="type">void</span> <span class="title">scale</span><span class="params">(<span class="type">double</span> k)</span> </span>{</span><br><span class="line"> <span class="built_in">setValue</span>(<span class="built_in">getValue</span>() * k);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你立即想到 </p>
<ol>
<li>你不需要 <code>Scaleable</code> 的动态绑定 </li>
<li>每个类都复制粘贴一样的代码十分愚蠢 </li>
<li>C++20 concept 不能提供默认成员实现(作者以为的。如果可以请告诉我)</li>
</ol>
<p>你知道可以用 CRTP!于是你设计出:</p>
<figure class="highlight cpp"><table><tr><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="keyword">struct</span> <span class="title class_">Scaleable</span> {</span><br><span class="line"> <span class="keyword">template</span> <<span class="keyword">typename</span> K></span><br><span class="line"> <span class="function">T &<span class="title">scale</span><span class="params">(K &&multiplicator)</span> </span>{</span><br><span class="line"> T &self = <span class="built_in">static_cast</span><T &>(*<span class="keyword">this</span>);</span><br><span class="line"> self.<span class="built_in">setValue</span>(self.<span class="built_in">getValue</span>() * multiplicator);</span><br><span class="line"> <span class="keyword">return</span> self;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Scaleable<Obj> {</span><br><span class="line"> <span class="type">double</span> value;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setValue</span><span class="params">(<span class="type">double</span> val)</span> </span>{ value = val; }</span><br><span class="line"> <span class="function"><span class="type">double</span> <span class="title">getValue</span><span class="params">()</span> </span>{ <span class="keyword">return</span> value; }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>这段代码很漂亮,还支持非退化的链式调用。最重要的是,它在 <code>Obj</code> 定义时就表明了 Scaleable 的语义。如果使用 C++20 concept 或者其他非成员函数去实现,<code>scale</code> 的实现就可能藏在某个 header 里,你不能一眼看出 <code>Obj</code> 是否 Scaleable,语义的表达就会被割裂。</p>
<p>聪明的你又想到了接口方法还可以是类外的方法:</p>
<figure class="highlight cpp"><table><tr><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">T &<span class="title">scale</span><span class="params">(Scaleable<T> &x, <span class="type">double</span> k)</span> </span>{</span><br><span class="line"> T &self = <span class="built_in">static_cast</span><T &>(x);</span><br><span class="line"> self.<span class="built_in">setValue</span>(self.<span class="built_in">getValue</span>() * k);</span><br><span class="line"> <span class="keyword">return</span> self;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>是不是很像 C++20 concept?我觉得很像!</p>
<h2 id="A-CRTP-Helper"><a href="#A-CRTP-Helper" class="headerlink" title="A CRTP Helper"></a>A CRTP Helper</h2><p>每次实现 CRTP 接口的时候,有很多重复工作要做,比如千篇一律的 <code>static_cast</code> 要写,还有 private ctor + friend class 定式,太繁琐。<br>本来使用 CRTP 的初衷就是简化代码,如今岂不是南辕北辙?</p>
<p>你想到可以抽象出所有的 CRTP 接口的公共部分,做成一个基类:</p>
<figure class="highlight cpp"><table><tr><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="keyword">struct</span> <span class="title class_">CRTP</span> {</span><br><span class="line"> <span class="function">T &<span class="title">self</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="built_in">static_cast</span><T &>(*<span class="keyword">this</span>); }</span><br><span class="line"> <span class="function"><span class="type">const</span> T &<span class="title">self</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> <span class="built_in">static_cast</span><<span class="type">const</span> T &>(*<span class="keyword">this</span>); }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="built_in">CRTP</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="keyword">friend</span> T;</span><br><span class="line">};</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="keyword">struct</span> <span class="title class_">Scaleable</span> : CRTP<T> { <span class="comment">/* ... */</span> };</span><br></pre></td></tr></table></figure>
<p>这看起来很美好,可是很快你又发现了问题:多个接口造成菱形继承:</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// diamond inheritance: CRTP<T></span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Obj</span> : Scaleable<Obj>, Lengthable<Obj> { </span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>这里使用虚继承无助于解决问题:被虚继承的基类是不能用 <code>static_cast</code> 向下转型的。</p>
<p>试过很多方案都不能解决菱形继承问题(如果有请告诉我),只能硬避开菱形继承了:</p>
<h3 id="Demo-2"><a href="#Demo-2" class="headerlink" title="Demo"></a>Demo</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T, <span class="keyword">template</span><<span class="keyword">typename</span>> <span class="keyword">class</span> <span class="title class_">Interface</span>></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">CRTP</span> {</span><br><span class="line"> <span class="function">T &<span class="title">self</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="built_in">static_cast</span><T &>(*<span class="keyword">this</span>); }</span><br><span class="line"> <span class="function"><span class="type">const</span> T &<span class="title">self</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> <span class="built_in">static_cast</span><<span class="type">const</span> T &>(*<span class="keyword">this</span>); }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="built_in">CRTP</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="keyword">friend</span> Interface<T>;</span><br><span class="line">};</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="keyword">struct</span> <span class="title class_">Scaleable</span> : CRTP<T, Scaleable> { <span class="comment">/* ... */</span> };</span><br><span class="line"></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="keyword">struct</span> <span class="title class_">Lengthable</span> : CRTP<T, Lengthable> { <span class="comment">/* ... */</span> };</span><br></pre></td></tr></table></figure>
<p>这样一来,<code>Scaleable</code> 和 <code>Lengthable</code> 的基类是两个不同的类,同时保持了零开销抽象,完美解决问题。</p>
<p>仔细观察你会发现,CRTP 避开菱形继承的手段恰好是 CRTP!</p>
<p>顺便提醒一句,调用 <code>self()</code> 的时候必须写 <code>this->self()</code>,否则编译器不知道正确的名字空间。</p>
<h2 id="Pros-amp-Cons"><a href="#Pros-amp-Cons" class="headerlink" title="Pros & Cons"></a>Pros & Cons</h2><p>你很可能已经感觉到把 CRTP 当作接口用有什么好处: </p>
<ol>
<li>接口表达了清晰、明确的语义; </li>
<li>既可以重写实现,又可以提供默认实现,十分灵活; </li>
<li>编译期内联能力,零开销抽象; </li>
<li>链式调用时类型不会退化。<br>但 CRTP 也有不足之处: </li>
<li>本质上是语言表达能力不足的妥协(还是推荐 concept); </li>
<li>要求类继承接口,不利于库设计; </li>
<li>难以表达两种接口的交集; </li>
<li>容易写 Bug,编译报错信息不友好; </li>
<li>宣告放弃动态绑定,需要谨慎设计。</li>
</ol>
<h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><ul>
<li><a href="https://www.bilibili.com/video/BV1pq4y1y7oN">双笙子佯谬 - 聊一聊C++设计模式、函数式编程等</a></li>
<li><a href="https://en.cppreference.com/w/cpp/utility/variant/visit">std::visit - cppreference.com</a></li>
<li><a href="https://www.cppstories.com/2019/02/2lines3featuresoverload.html/">2 Lines Of Code and 3 C++17 Features - The overload Pattern - C++ Stories</a></li>
<li><a href="https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern">Curiously recurring template pattern - Wikipedia</a></li>
<li><a href="https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/">What the Curiously Recurring Template Pattern can bring to your code - Fluent C++</a></li>
<li><a href="https://www.fluentcpp.com/2017/05/19/crtp-helper/">An Implementation Helper For The Curiously Recurring Template Pattern - Fluent C++</a></li>
<li><a href="http://baiy.cn/doc/cpp/inside_rtti.htm">白杨 - RTTI、虚函数和虚基类的实现方式、开销分析及使用指导</a></li>
</ul>
]]></content>
<tags>
<tag>C++</tag>
</tags>
</entry>
<entry>
<title>C++ 标准笔记</title>
<url>/2021/05/03/C/</url>
<content><![CDATA[<p>本文始于 2018/6/27,用于汇聚关于 C++ 的实用知识和碰到的坑,温故而知新。</p>
<blockquote>
<p>2022/1/2 【更改】标题改为《C++ 标准笔记》<br>2022/1/2 【新增】StandardLayout<br>2022/1/2 【新增】bit field</p>
<p>更新预告:</p>
<ol>
<li>CRTP 原理,目的和应用</li>
<li>CRTP 应用之表达式模板</li>
</ol>
</blockquote>
<span id="more"></span>
<h2 id="union-共用体"><a href="#union-共用体" class="headerlink" title="union 共用体"></a>union 共用体</h2><ul>
<li>union可匿名,常在结构定义中。</li>
<li>用于节省内存,尤其在嵌入式系统中。</li>
<li>union的定义形式与struct相同。</li>
</ul>
<hr>
<h2 id="enum-枚举"><a href="#enum-枚举" class="headerlink" title="enum 枚举"></a>enum 枚举</h2><ul>
<li>enum A {a,b,c}; 首项默认为0,默认后项比前项大一。<br>A被看做一种类型,甚至可以省略;a,c,b被看做常量。<br>a,b,c可以自动提升为int, 但int不能自动转换为枚举类型,除非强制转换A(1)。<br>每个enum根据其常量的最大最小值确定其上下限。</li>
<li>enum创建的常量是静态的, 可以用作静态类成员常量. 运行时所有对象不会包含枚举</li>
<li><strong>更强的安全性--类作用域内枚举(C++11)</strong><ul>
<li>enum class name : type {...};</li>
<li>enum struct name {...};</li>
<li>class或struct二选一, <code>:type</code>可选.</li>
<li>作用域内枚举不允许隐式地转换为整型</li>
<li>默认底层为int</li>
<li>调用格式为name::x</li>
</ul>
</li>
<li>:type 放在枚举名后以指定底层, 否则将随实现而异.</li>
</ul>
<hr>
<h2 id="char-字符型"><a href="#char-字符型" class="headerlink" title="char 字符型"></a>char 字符型</h2><ul>
<li>char类型被输入输出流区别对待。cout << (int*) st << endl;</li>
<li>char类型数组的初始化被C++区别对待:<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="type">char</span> s1[<span class="number">5</span>] = <span class="string">"abcd"</span>; 合法,<span class="string">"abcd"</span>被转换为{<span class="string">'a'</span>,<span class="string">'b'</span>,<span class="string">'c'</span>,<span class="string">'d'</span>}, 可供修改</span><br><span class="line"><span class="type">char</span> s2[<span class="number">5</span>] = s1; 错误,即使s1是<span class="type">const</span>。</span><br><span class="line"><span class="type">char</span>* s3 = <span class="string">"abcd"</span>; 警告, <span class="string">"abcd"</span>是常量,不能修改. 参数<span class="type">char</span> s3[]=<span class="string">"abcd"</span>亦等价警告.</span><br></pre></td></tr></table></figure></li>
<li><code>const char []</code>所定义的字符串被保护,有内存位置隔离,不允许跟踪(但允许查看)地址。</li>
<li>char是否带符号取决于系统。</li>
<li>wchar_t可以表示系统使用的最大扩展字符集,输入输出用wcin,wcout</li>
<li>raw r"(a/b\c回车defg*)", 标识符 "( )" 可变为 "*@( )*@" 等等,保持平移对称</li>
</ul>
<h3 id="lt-cstring-gt-常用函数"><a href="#lt-cstring-gt-常用函数" class="headerlink" title="<cstring>常用函数"></a><cstring>常用函数</h3><ul>
<li><code>char* strcpy(char* dest, const char* src)</code><ul>
<li>以src覆盖dest, 直到遇到src的终止符.返回dest.</li>
</ul>
</li>
<li><code>char* strncpy(char* dest, const char* src, size_t count)</code><ul>
<li>以src的前n位覆盖dest, 若遇到src的终止符则用0填充剩余位.返回dest.</li>
</ul>
</li>
<li><code>char* strcat(char *dest, char *src)</code><ul>
<li>将src复制到dest的末尾.返回dest.</li>
</ul>
</li>
<li><code>int strcmp(char *str1, char *str2)</code><ul>
<li>按字典序比较, 返回-1,0,1.</li>
</ul>
</li>
<li><code>int stricmp(char *str1, char *str2)</code><ul>
<li>按字典序但对大小写不敏感比较.</li>
</ul>
</li>
<li><code>int strncmp(char *str1, char *str2, size_t count)</code><ul>
<li>按字典序比较前n位</li>
</ul>
</li>
<li><code>int strnicmp(char *str1, char *str2, size_t count)</code><ul>
<li>按字典序但对大小写不敏感比较前n位</li>
</ul>
</li>
</ul>
<hr>
<h2 id="vector与array-模板类"><a href="#vector与array-模板类" class="headerlink" title="vector与array 模板类"></a>vector与array 模板类</h2><ul>
<li><code>vector<typename> arr(n_elem);</code> 也可以不指定长度</li>
<li><code>array<typename, n_elem> arr;</code> 定长数组。 等长数组可以直接复制。</li>
<li>下标可越界,欲防止越界用 <code>arr.at(x)</code></li>
</ul>
<hr>
<h2 id="读入行"><a href="#读入行" class="headerlink" title="读入行"></a>读入行</h2><ul>
<li><code>cin.getline(arr, arsize)</code> 空行不设置failbit</li>
<li><code>cin.get(arr, arsize)</code> 行读取结束后不丢弃换行符(无法跨越)</li>
<li><code>cin.get()</code> 读取一个任意字符,返回char值</li>
<li><code>cin.get(char)</code>返回cin</li>
<li><code>string</code>类读行 <code>getline(cin, str);</code></li>
</ul>
<hr>
<h2 id="fstream-文件流(以fin-fout为例)"><a href="#fstream-文件流(以fin-fout为例)" class="headerlink" title="fstream 文件流(以fin, fout为例)"></a>fstream 文件流(以fin, fout为例)</h2><ul>
<li>打开文件: <code>.open("filename");</code></li>
<li>关闭文件: <code>.close();</code></li>
<li>检测最后一次读入遇到EOF: <code>fin.eof();</code></li>
<li>检测最后一次读入遇到类型不匹配(包括EOF): <code>fin.fail();</code></li>
<li>检测最后一次读入文件损坏、故障: <code>fin.bad();</code></li>
<li>检测最后一次读入完全正常: <code>fin.good();</code> 等价于 <code>(bool) fin >> value;</code></li>
<li>清空错误标记,准许读入: <code>.clear();</code></li>
</ul>
<hr>
<h2 id="switch-结构控制"><a href="#switch-结构控制" class="headerlink" title="switch 结构控制"></a>switch 结构控制</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">switch</span>(num)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">case</span> constValue1 : statement1</span><br><span class="line"> <span class="keyword">break</span>;(optional)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">case</span> constValue2 : statement2</span><br><span class="line"> <span class="keyword">break</span>;(optional)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> : statement3</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<hr>
<h2 id="杂项"><a href="#杂项" class="headerlink" title="杂项"></a>杂项</h2><ul>
<li><code>exit(EXIT_FAILURE);</code> : cstdlib, 同操作系统通信的参数值</li>
<li><code>nullptr</code> : C++11 关键字, 空指针值.</li>
</ul>
<hr>
<h2 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h2><ul>
<li>应当将空指针写为<code>nullptr</code>. <code>delete</code>和<code>delete []</code> 都可以作用于空指针.</li>
<li>const 指针 将受保护,非const 指针 不能复制其值(除非利用强制类型转换),不允许通过const 指针 修改所指向值。<br>为了防止欺骗const检查,不允许令const 二级指针指向非const 一级指针</li>
</ul>
<hr>
<h2 id="函数指针"><a href="#函数指针" class="headerlink" title="函数指针"></a>函数指针</h2><ul>
<li>声明:<code>typename (*pointer name)(parameter list...);</code></li>
<li>获得某函数的地址:<code>pointer = function name;</code></li>
<li>使用函数指针:<code>(*pointer)(parameter list...);</code> 或 <code>pointer(parameter list...);</code></li>
<li>举例: <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">原函数:</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">const</span> <span class="type">double</span>* <span class="title">f1</span><span class="params">(<span class="type">const</span> <span class="type">double</span> [], <span class="type">int</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">指向f1的指针:</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">double</span>* (*p1)(<span class="type">const</span> <span class="type">double</span> [], <span class="type">int</span>) = f1; 或 <span class="keyword">auto</span> p1 = f1;</span><br><span class="line"></span><br><span class="line">由f1,f2,f3组成数组:<span class="comment">//[]优先级高于*</span></span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">double</span>* (*ar[<span class="number">3</span>])(<span class="type">const</span> <span class="type">double</span> [], <span class="type">int</span>) = {f1, f2, f3}; 不允许<span class="keyword">auto</span></span><br><span class="line"></span><br><span class="line">指向ar[<span class="number">3</span>]的指针:</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">double</span>* (*(*arp)[<span class="number">3</span>])(<span class="type">const</span> <span class="type">double</span>[], <span class="type">int</span>) = &ar; 或 <span class="keyword">auto</span> arp = &ar;</span><br><span class="line"></span><br><span class="line">还可以用<span class="keyword">typedef</span>简化声明:</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">const</span> <span class="type">double</span>* (*p_fun)<span class="built_in">const</span>(<span class="type">const</span> <span class="type">double</span> [], <span class="type">int</span>);</span><br><span class="line"></span><br><span class="line">p_fun ar[<span class="number">3</span>] = {f1, f2, f3};</span><br></pre></td></tr></table></figure></li>
<li>分析方法: 从变量名开始, 往右往左结合, 逐步解释结合体.<ul>
<li>当遇到一个指针, 总是关心它指向什么类型</li>
<li>当遇到一个数组, 总是关心它的元素的类型</li>
<li>当遇到一个函数, 总是关心它的返回值类型</li>
</ul>
</li>
</ul>
<hr>
<h2 id="参数传递"><a href="#参数传递" class="headerlink" title="参数传递"></a>参数传递</h2><ul>
<li>参量(argument)是实参,参数(parameter)是形参。</li>
</ul>
<hr>
<h2 id="函数重载(函数多态)"><a href="#函数重载(函数多态)" class="headerlink" title="函数重载(函数多态)"></a>函数重载(函数多态)</h2><ul>
<li>同名函数共存要求:函数特征标(函数参数列表)不同。</li>
<li>当需要自动类型转换但选项不唯一时,编译不通过。</li>
<li>不区分const和非const变量(包括指针)。</li>
<li>区分指向const的指针和指向非const的指针。</li>
<li>区分指向const的引用和指向非const的引用。(编译器调用最匹配的版本)</li>
<li>如果没有右值引用参数版本,右值参量将赋给const引用参量。</li>
</ul>
<hr>
<h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><ul>
<li>左值引用<ul>
<li>非const引用可以引自可修改的左值(防止修改const值和修改临时变量)</li>
<li>const引用可以引自任何左值、右值(毕竟数值不会变)</li>
<li>基类引用可以指向派生类对象</li>
</ul>
</li>
<li>指针也能被引用, 例如 int*& x = ptr;</li>
</ul>
<hr>
<h2 id="短路运算符"><a href="#短路运算符" class="headerlink" title="短路运算符"></a>短路运算符</h2><ul>
<li>||, &&具有短路作用,结果必然时短路,不计算右边的表达式</li>
</ul>
<hr>
<h2 id="inline"><a href="#inline" class="headerlink" title="inline"></a>inline</h2><ul>
<li>在声明时用或在第一次调用前的定义用都行</li>
<li>不能处理递归, 编译器有权拒绝采用.</li>
<li>函数默认值必须放在原型声明中, 不能放在和声明分离的定义中.只能从右到左地提供默认值</li>
</ul>
<hr>
<h2 id="decltype-expression"><a href="#decltype-expression" class="headerlink" title="decltype(expression)"></a>decltype(expression)</h2><ul>
<li><p><strong>decltype(expression)核对表</strong></p>
<ol>
<li>如果expression是一个不带括号的标识符, 则返回相同类型, 包括const,*等限定符</li>
<li>否则,如果expression是一个函数调用(需要提供参数),则返回相同类型</li>
<li>否则,如果expression是一个带括号的左值,则返回其引用</li>
<li>否则,返回expression的类型(例:右值)</li>
</ol>
</li>
<li><p>可以利用<strong>typedef</strong> decltype(expression) somename;简化声明</p>
</li>
<li><p><strong>decltype 用于函数返回类型声明</strong></p>
<ul>
<li>声明返回类型时未知参数列表, 所以需要后置声明 <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T1, <span class="keyword">class</span> <span class="title class_">T2</span>></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">gt</span><span class="params">(T1 x, T2 y)</span> -> <span class="title">decltype</span><span class="params">(x + y)</span> <span class="comment">// "-> 已知类型" 也行</span></span></span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
<hr>
<h2 id="模板"><a href="#模板" class="headerlink" title="模板"></a>模板</h2><ul>
<li><p><strong>创建模板</strong></p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T> <span class="comment">//也可以用class代替typename</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">swap</span><span class="params">(T&, T&)</span></span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>显式具体化</strong></p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">template</span> <> <span class="function"><span class="type">void</span> <span class="title">swap</span><span class="params">(<span class="type">int</span>&, <span class="type">int</span>&)</span></span>;</span><br><span class="line"><span class="keyword">template</span> <> <span class="type">void</span> <span class="built_in">swap</span><<span class="type">int</span>>(<span class="type">int</span>&, <span class="type">int</span>&);</span><br></pre></td></tr></table></figure>
<p> <em>为什么需要显示具体化?常规函数不能替代吗?</em></p>
</li>
<li><p><strong>显式实例化</strong></p>
<ul>
<li>不使用函数就能依照模板生成实例, 常用在库中</li>
<li><code>template void swap<int>(int&, int&);</code></li>
<li>使用函数时<code>add<double>((int)a, (double)b);</code> 会强制使用<double>模板.注意引用类型不能隐式类型转换.后续的强制使用仍需加<double></li>
</ul>
</li>
</ul>
<hr>
<h2 id="变量名"><a href="#变量名" class="headerlink" title="::变量名"></a>::变量名</h2><ul>
<li>表明采用全局变量而非局部变量</li>
</ul>
<hr>
<h2 id="储存空间"><a href="#储存空间" class="headerlink" title="储存空间"></a>储存空间</h2><ol>
<li>静态储存: 保证初始化为0, 生命周期和程序相同.</li>
<li>自动储存(栈内存): 不保证初始化;</li>
<li>动态储存(堆内存): 不保证初始化;</li>
</ol>
<hr>
<h2 id="储存说明符"><a href="#储存说明符" class="headerlink" title="储存说明符"></a>储存说明符</h2><ul>
<li>static用于变量 <em>编译阶段尽量初始化, 运行时直接分配空间,程序结束时销毁, 首次遇到时再保证初始化</em><ul>
<li>静态外部链接性变量: 直接在非被包括区域定义, 在其他单元中可用extern引用声明(不允许再次初始化)(ODR单定义规则)</li>
<li>静态内部链接性变量: 在非被包括区域加static, 限定在本单元内可以访问</li>
<li>静态无链接性变量:在被包括区域加static, 限定在本包括区域内可以访问</li>
</ul>
</li>
<li>static用于函数 覆盖函数的默认外部链接性为内部链接性,必须同时用于原型和定义中.</li>
<li>thread_local 变量持续性与所属线程的持续性相同, 可与static, extern结合使用(其他声明中不能使用多个储存说明符)</li>
<li>mutable 使得const对象中的属性能被修改,而不受const限制</li>
<li>register C++11之前是寄存器变量,不能取地址; 之后是显式指明自动变量(无实际作用,避免旧代码非法)</li>
</ul>
<hr>
<h2 id="显式指出语言链接性以帮助链接程序寻找匹配函数"><a href="#显式指出语言链接性以帮助链接程序寻找匹配函数" class="headerlink" title="显式指出语言链接性以帮助链接程序寻找匹配函数:"></a>显式指出语言链接性以帮助链接程序寻找匹配函数:</h2><pre><code>extern "C" void spiff(int);
</code></pre>
<hr>
<h2 id="cv-限定符"><a href="#cv-限定符" class="headerlink" title="cv-限定符"></a>cv-限定符</h2><pre><code>- const全局变量带有隐式static使得变量链接性为内部, 导致多文件同时include一个头文件时不会发生定义冲突 *可以使用extern覆盖隐式static使变量链接性为外部. 在其他单元中仍需用extern来引用它.*
- volatile 提示编译器该变量会在程序之外被修改, 不要进行寄存器缓存优化. 多见于驱动程序.
</code></pre>
<hr>
<h2 id="new运算符"><a href="#new运算符" class="headerlink" title="new运算符"></a>new运算符</h2><ul>
<li>原函数: <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> * <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="type">size_t</span>)</span></span>;</span><br><span class="line"><span class="type">void</span> * <span class="keyword">operator</span> <span class="keyword">new</span>[] (std::size_T); <span class="comment">// new int[40] --> new(40*sizeof(int))</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="type">void</span> *)</span></span>;</span><br><span class="line"><span class="type">void</span> <span class="keyword">operator</span> <span class="keyword">delete</span>[] (<span class="type">void</span> *);</span><br></pre></td></tr></table></figure></li>
<li>new, delete函数可以替换.</li>
<li>new在堆中查找满足要求的内存块</li>
<li>定位new运算符<ul>
<li><code>typename* p = new (MemAddress) typename</code></li>
<li>返回(void *)指定的内存地址</li>
<li>允许重载</li>
<li><strong>注意非堆内存不可delete</strong></li>
<li><strong>若内存中存放了对象,则需要手动调用析构函数,再由MemAddress释放内存,且需注意new []与delete []对应</strong></li>
</ul>
</li>
<li>元素的创建与销毁应遵循FILO顺序</li>
</ul>
<hr>
<h2 id="名称空间"><a href="#名称空间" class="headerlink" title="名称空间"></a>名称空间</h2><ul>
<li>声明区域: 可以声明变量的区域, 例如所在的文件, 代码块.</li>
<li>潜在作用域: 从声明点开始到声明区域的结尾. 可能被局部变量隐藏,故称潜在.</li>
<li>自定义名称空间:<code>namespace Somewhat {...}</code><ul>
<li>只能在函数外部定义, 允许在另一个名称空间中嵌套</li>
<li>因此, 默认情况下声明的名称的链接性为外部(除非它引用了常量)</li>
<li>可以在其他合法位置继续添加名称, 提供定义.</li>
</ul>
</li>
<li><code>using namespace Somewhat;</code>编译指令<ul>
<li>每个声明区域中都有一条隐式的<code>using namespace 全局名称空间;</code></li>
<li>若某处使用过<code>using namespace</code>编译指令,则其子声明区域也隐式添加这条语句.</li>
<li>局部变量拥有最高优先权,能隐藏各种using namespace同名变量(因为名称空间都在函数外部定义)</li>
</ul>
</li>
<li><code>using</code>声明<ul>
<li>类似于声明了一个局部变量, 在代码块中不允许同级同名.</li>
<li>因此使用using声明比使用using编译指令更安全.</li>
</ul>
</li>
<li>名称空间可以拥有别名, 用于简化代码: namespace MEF = myth::elements::fire;</li>
<li>名称空间可以匿名, 声明之后自动隐式using, 用于避免其他using并替代static全局变量.</li>
<li><.h>文件是不支持名称空间的版本.新版一般将函数搬到std中.</li>
<li>使用建议: 在大型程序/多单元程序使用<ul>
<li>少用<code>using namespace</code></li>
<li>避免在头文件中使用using编译指令,若必须使用,则在所有#include之后使用</li>
<li>避免直接声明外部全局变量和静态全局变量,改用已命名的名称空间</li>
<li>using声明首选用在局部而不是全局</li>
</ul>
</li>
</ul>
<hr>
<h2 id="类"><a href="#类" class="headerlink" title="类"></a>类</h2><ul>
<li>构造函数<ul>
<li>调用示例: <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">Classname object; <span class="comment">// 调用空参数构造函数</span></span><br><span class="line"><span class="function">Classname <span class="title">object</span><span class="params">()</span></span>; <span class="comment">// 警告, 正在声明函数</span></span><br><span class="line"><span class="function">Classname <span class="title">object</span><span class="params">(one, two, ...)</span></span>; <span class="comment">// 调用对应的构造函数</span></span><br><span class="line">Classname object = <span class="built_in">Classname</span>(one, ...); <span class="comment">// 有可能先构造临时对象</span></span><br><span class="line">Classname* p = <span class="keyword">new</span> <span class="built_in">Classname</span>(...);</span><br><span class="line">Classname object{...}; Classname object = Classname{...}; <span class="comment">// C++11</span></span><br><span class="line">Classname* p = <span class="keyword">new</span> Classname{}; <span class="comment">// C++11</span></span><br><span class="line">Classname object = value; <span class="comment">// 调用一个参数的构造函数, 可用explicit(修饰构造函数)关闭这种特性</span></span><br></pre></td></tr></table></figure></li>
<li>重载构造函数中使用的new或new []必须对应,因为析构函数只有一个。</li>
<li>初始化列表<ul>
<li>参数列表后由冒号引出的初始化信息。在此初始化对象将使用复制构造函数,而不是空构造函数加赋值运算符,因此效率更高。</li>
<li>初始化的顺序按照成员变量的声明顺序,而与初始化列表顺序无关。</li>
<li>一旦进入花括号,成员变量将完成默认的初始化,对象初步创建完成。因此,成员变量中,非静态常量与引用必须由此列表进行初始化。</li>
<li>类内初始化可等价于默认的列表初始化。列表初始化会覆盖类内初始化。</li>
<li>初始化列表负责调用基类构造函数(基类已在派生类类域中,无需加作用域解析符)</li>
<li>初始化列表负责调用成员对象的非默认构造函数</li>
</ul>
</li>
<li>默认不能继承构造函数(C++11)</li>
</ul>
</li>
<li>析构函数<ul>
<li>只能有一个析构函数, 且参数必须为空</li>
<li>注意用delete对应构造函数或其他过程的new</li>
<li>若对象由定位new运算符创建,则需要手动调用析构函数,且遵循FILO顺序。</li>
<li>必须以delete或delete []对应构造函数中的new或new []。</li>
<li>最后会自动调用基类的析构函数</li>
<li>应当将析构函数声明为虚函数</li>
<li>不能继承析构函数</li>
</ul>
</li>
<li>封装是一个编译期的概念<ul>
<li>编译期不存在实例,编译器仅针对类型做检查</li>
<li>可以在类方法中访问同类对象的私有成员</li>
</ul>
</li>
<li><em>同struct</em> : 避免环形构造<ul>
<li>编译器禁用简单环形定义, 如 <code> struct A { A a; }; // 使用了未完成的定义</code></li>
<li>编译器不能辨别复杂环形定义, 如<code>struct A { A(){} A* a = new A(); }</code></li>
</ul>
</li>
<li>非静态变量在运行时才会创建, 所以如int arr[MAXN]的MAXN必须是静态量,可以是全局const, 类中static const, enum{MAXN=x}.</li>
<li>友元函数<ul>
<li>在共有部分声明友元函数原型, 也可以紧接定义以设为内联函数.后置的定义无需friend修饰</li>
<li>友元函数视为类外函数, 但可以访问类私有成员变量.</li>
<li>类的显式二元重载运算符应当用友元, 尽管没有直接修改类私有成员变量</li>
<li>重载<< : <code>std::ostream& operator << (const std::ostream& os, const Classname& obj)</code>以方便输出.</li>
<li>友元函数可方便隐式类型转换, 不必苛求由对象发起函数调用.例如cmp("asd", obj),可以对应原型cmp(string, string);</li>
<li>可以在派生类友元函数中,强制向上转型并使用基类友元函<br> 数据类型, 否则易有二义性<br> 函数, 避免隐式转换出错<br> ass_name&)`<br> 始化、按值传递、按值返回、上转型并使用基类友元函数。</li>
</ul>
</li>
<li>转换函数<code>operator typeName()</code><ul>
<li>用途:将类转换成基础数据类型</li>
<li>必须是成员方法</li>
<li>不能指定返回类型</li>
<li>不能有参数</li>
<li>用于cout时应显式标明类型</li>
<li>应当用explicit修饰</li>
</ul>
</li>
<li>复制构造函数<code>Class_name(const Class_name&)</code><ul>
<li>在初始化对象时使用(显示的初始化、按值传递、按值返回、编译器生成临时对象)</li>
<li><strong>新版本C++可用移动构造函数</strong></li>
<li>例: <figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="function">Class_name <span class="title">a</span><span class="params">(b)</span></span>;</span><br><span class="line">Class_name* pa = <span class="keyword">new</span> <span class="built_in">Class_name</span>(b);</span><br><span class="line">Class_name a = b; <span class="comment">// 可能生成临时对象后调用赋值运算符函数,根据实现而异.</span></span><br><span class="line">Class_name a = <span class="built_in">Class_name</span>(b); <span class="comment">// 同上</span></span><br><span class="line">按值传递函数调用<span class="built_in">func</span>(b); <span class="comment">// 按值传递也初始化了参数</span></span><br></pre></td></tr></table></figure></li>
<li>默认的复制构造函数,是在初始化列表中调用所有成员的复制构造函数。默认复制派生类对象的基类部分</li>
<li>应注意<strong>深度复制</strong>, 即妥善处理指针所指向内容的复制</li>
</ul>
</li>
<li>赋值运算符函数<ul>
<li>考虑妥善处理原来已有的,即将被抛弃的数据.</li>
<li>考虑自己赋值给自己的情况, 小心赋值前删除了自身数据.</li>
<li>记住返回自身引用, 即 <code>return *this</code></li>
<li>不能继承赋值运算符函数</li>
<li>不建议设置为虚方法,为了避免同基类的不同派生类互相赋值</li>
</ul>
</li>
<li>默认方法的潜在危险<ul>
<li>某个类在开发初期不需要复制构造函数/赋值运算符重载,但在以后可能需要</li>
<li>可能写出符合常理的代码,但由于未覆盖默认方法而运行时异常</li>
<li>可能在未察觉的情况下调用了默认方法(用重载加法时按值传递、按值返回将调用复制构造函数)</li>
<li>解决办法:<ul>
<li>不管默认方法是否需要,总是提供正确的代码</li>
<li>将空方法设为private,并留下错误信息</li>
<li>使用delete(C++11)</li>
</ul>
</li>
</ul>
</li>
<li><code>[ ]</code>(取位)运算符: 两个操作数分立于左中括号两侧。只能是成员函数。</li>
<li>const对象<ul>
<li>只能使用const函数, 若返回引用, 则返回类型为const type&</li>
</ul>
</li>
<li>静态类成员函数<ul>
<li>原型含static, 定义不含</li>
<li>和Java不同, 不能通过对象来调用静态成员函数</li>
<li>调用格式 <code>Class_name::s_method();</code></li>
</ul>
</li>
<li>静态类成员变量<ul>
<li>在类中声明, 不可定义</li>
<li>在类外定义并分配内存, 可以不初始化</li>
<li>静态常量在声明同时定义.</li>
</ul>
</li>
<li>派生/继承<ul>
<li><code>class newClass : public baseClase</code></li>
<li>默认继承为private继承。私有继承的向上转型必须显式写出</li>
<li>多重继承的向上转型也应显示写出,以防不同基类的方法冲突</li>
<li>派生类中一旦定义某方法与基类方法同名,则基类所有该名方法被隐藏。与参数列表(特征标)无关。<ul>
<li>可用基类名::方法名的途径调用隐藏的方法</li>
<li>重新定义继承的方法,应确保与原来的原型完全相同</li>
<li>若返回类型为基类引用或指针,则可以修改为派生类的引用或指针(返回类型协变)</li>
<li>若基类虚方法被重载了,则应在派生类中重新定义所有的基类版本</li>
</ul>
</li>
<li>若派生类构造函数使用了new,则应提供复制构造函数/赋值运算符(含<code>base::operator=(o);</code>)/虚析构函数的定义</li>
<li>公有继承表达了is-a关系,私有继承/包含表达了has-a关系。通常使用组合包含层次化来建立has-a关系。如果需要访问原有类的保护成员或重新定义虚方法,才使用私有继承。</li>
<li>欲调用基类对象,对(*this)强制向上转型(const Base &)即可</li>
<li>用using改变继承成员权限:在派生类的public处<code>using Base::methodName;</code>(省略using的技术是即将被抛弃的旧技术)</li>
</ul>
</li>
<li>虚方法<ul>
<li>若希望通过基类引用或指针调用派生类方法,则需要将基类方法声明为virtual虚方法(一般也将派生类方法声明为virtual)</li>
<li>应当将析构函数定义为虚方法,以确保正确地销毁对象</li>
<li>在类中欲调用基类方法(而不是本类方法),需使用作用域解析符<code>Base::baseMethod();</code></li>
<li>编译器对虚方法的一般实现:把类的所有虚函数的地址制作成表,在对象中添加隐藏的虚函数表指针,在运行时通过指针检索虚函数。</li>
<li>与Java不同,派生类虚方法的访问权限允许变严格,但由基类引用或指针的多态效果仍然生效。</li>
<li>派生类中一旦定义虚方法,就将隐藏基类所有同名方法,故应在派生类中重新定义所有的基类重载方法</li>
<li>可以令虚方法声明结尾处为<code>=0</code>,使方法成为纯虚方法。纯虚方法可以不提供定义。含有至少一个纯虚方法的类为抽象类,不能声明对象。</li>
</ul>
</li>
<li>访问权限<ul>
<li>private:仅本类和友元函数有权访问。对数据成员的理想访问控制。</li>
<li>protected:本类和派生类有权访问。在派生链中,此权限类似public;在类外部,此权限类似peivate。此权限通常只给成员函数。</li>
<li>public:在同一域中就能访问。</li>
</ul>
</li>
<li>引用限定成员函数<ul>
<li>函数签名的最后可以标注<code>&</code>或<code>&&</code>来做「引用限定 ref-qualified」。<ol>
<li>非限定成员函数可由左值调用,也可由右值调用。此时不能再定义引用限定成员函数。</li>
<li>左值限定成员函数仅可由左值调用。</li>
<li>右值限定成员函数仅可由右值调用。</li>
</ol>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="Lambda"><a href="#Lambda" class="headerlink" title="Lambda"></a>Lambda</h2><p>Lambda是一种匿名函数,它在普通函数的基础上增加了一些功能。Lambda是通过函数对象实现的,具有在编译器内联的能力,因此性能可以比函数指针更好。</p>
<h3 id="使用举例"><a href="#使用举例" class="headerlink" title="使用举例"></a>使用举例</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">[](<span class="type">int</span> q, <span class="type">int</span> p) { <span class="keyword">return</span> q > p; } <span class="comment">// 常见的比较函数</span></span><br><span class="line">[mask](<span class="type">int</span> x) { <span class="keyword">return</span> mask & x; } <span class="comment">// 判断有无交集</span></span><br><span class="line">[&flag](<span class="type">int</span> x) { flag[x] = <span class="literal">true</span>; } <span class="comment">// 修改自动变量flag数组</span></span><br><span class="line">[&](<span class="type">int</span> x)-><span class="type">int</span> { <span class="comment">// 按引用捕获当前作用域的全部自动变量。不是简单地return故要声明类型</span></span><br><span class="line"> <span class="keyword">if</span> (x > mx) mx = x;</span><br><span class="line"> <span class="keyword">return</span> tot += x;</span><br><span class="line">}</span><br><span class="line">[k, &](<span class="type">int</span> del) { <span class="comment">// 按值捕获k、按引用捕获其他变量</span></span><br><span class="line"> sth += del;</span><br><span class="line"> cout << k << endl; <span class="comment">// k是只读的??!!</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<hr>
<h2 id="右值"><a href="#右值" class="headerlink" title="右值"></a>右值</h2><p>rvalue,(非严格定义: )匿名的临时值,常出现在等号的右方。最大特征是不可取地址,例如</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> a;</span><br><span class="line">a = <span class="number">2</span> + <span class="number">3</span>; <span class="comment">// (2+3) 就是一个右值, &(2+3)没有意义</span></span><br></pre></td></tr></table></figure>
<h3 id="右值引用"><a href="#右值引用" class="headerlink" title="右值引用"></a>右值引用</h3><p>有时候运算过程中会生成一些体积大的临时对象,在完成表达式后这些临时对象会析构,例如</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">MyString</span> {</span><br><span class="line"> <span class="type">char</span>* data = <span class="literal">nullptr</span>;</span><br><span class="line"> <span class="comment">//... 使用new和delete的模仿string类,具有拷贝构造函数</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">vector<MyString> arr; arr.<span class="built_in">reverse</span>(<span class="number">10</span>); <span class="comment">// 保留10个string的空间</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">10</span>; ++i) {</span><br><span class="line"> arr.<span class="built_in">push_back</span>(<span class="built_in">MyString</span>(<span class="string">"Hello_rvalue"</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>第8行发生如下事件:</p>
<ol>
<li>MyString("Hello_rvalue") 产生一个右值。</li>
<li>push_back接收一个右值参数,并调用MyString的<strong>拷贝构造函数</strong>新建一个对象。</li>
<li>MyString("Hello_rvalue")生命周期结束,析构。</li>
</ol>
<p>发现拷贝构造函数存在资源浪费:既然右值的资源已经没有后续价值,大可以将其资源“偷”过来使用。</p>
<p>于是,MyString类新增<strong>移动构造函数</strong>(还有<strong>移动赋值函数</strong>)</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">MyString</span> {</span><br><span class="line"> <span class="comment">// ... 同上</span></span><br><span class="line"> <span class="built_in">MyString</span>(MyString&& x) {</span><br><span class="line"> <span class="keyword">this</span>->data = x->data;</span><br><span class="line"> x->data = <span class="literal">nullptr</span>; <span class="comment">// 资源被偷走</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"> MyString& <span class="keyword">operator</span>=(MyString&& x) {</span><br><span class="line"> <span class="comment">// 类似移动构造函数,不赘述</span></span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>如此一来,push_back将自动调用移动构造函数来创建对象,避免大量new内存。</p>
<p>右值引用的存在,就是为了<strong>尽量榨干临时对象的价值</strong>。</p>
<p>要利用右值引用,最重要的是<strong>合理地定义移动构造和移动赋值</strong>。</p>
<h3 id="移动语义"><a href="#移动语义" class="headerlink" title="移动语义"></a>移动语义</h3><p>有时候一些左值就像右值一样即将消亡,弃之可惜,例如</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<<span class="number">10</span>; ++i) {</span><br><span class="line"> <span class="function">string <span class="title">tmp</span><span class="params">(<span class="string">"hello"</span>)</span></span>;</span><br><span class="line"> tmp += <span class="string">" rvalue?"</span>; <span class="comment">// tmp经过一系列复杂的处理</span></span><br><span class="line"> arr.<span class="built_in">push_back</span>(tmp);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果能像右值引用一般把tmp的资源“偷”走就好了,于是</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<<span class="number">10</span>; ++i) {</span><br><span class="line"> <span class="function">string <span class="title">tmp</span><span class="params">(<span class="string">"hello"</span>)</span></span>;</span><br><span class="line"> tmp += <span class="string">" rvalue?"</span>; <span class="comment">// tmp经过一系列复杂的处理</span></span><br><span class="line"> arr.<span class="built_in">push_back</span>(std::<span class="built_in">move</span>(tmp)); <span class="comment">// 强制转换为右值</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>注意,move(tmp)后由于资源已经被偷走,tmp可能像野指针一样危险。被move后对象的行为由程序员负责。</p>
<h3 id="完美转发"><a href="#完美转发" class="headerlink" title="完美转发"></a>完美转发</h3><p>完美转发是针对c++模板函数的概念。</p>
<p>我们不得不先介绍c++引用折叠概念:</p>
<table>
<thead>
<tr>
<th>typename T</th>
<th>T&</th>
<th>T&&</th>
</tr>
</thead>
<tbody><tr>
<td>G</td>
<td>G&</td>
<td>G&&</td>
</tr>
<tr>
<td>G&</td>
<td>G&</td>
<td><strong>G&</strong></td>
</tr>
<tr>
<td>G&&</td>