-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
123 lines (58 loc) · 365 KB
/
search.xml
File metadata and controls
123 lines (58 loc) · 365 KB
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>设计模式</title>
<link href="/2025/02/06/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<url>/2025/02/06/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/</url>
<content type="html"><{m_iprogress-><span class="built_in">DoProgress</span>(context);})</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MainForm</span> : <span class="keyword">public</span> Form , <span class="keyword">public</span> Iprogress { <span class="comment">//观察者</span></span><br><span class="line"> TextBox* txtFilePath; <span class="comment">// 所要分割的文件路径</span></span><br><span class="line"> TextBox* txtFileNumber; <span class="comment">// 所要分割的文件数目</span></span><br><span class="line"> ProcessBar* processBar; <span class="comment">// 新增进度条</span></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Button1_Click</span><span class="params">()</span> </span>{</span><br><span class="line"> string filePath = txtFilePath-><span class="built_in">getText</span>();</span><br><span class="line"> <span class="type">int</span> number = <span class="built_in">atoi</span>(txtFileNumber-><span class="built_in">getText</span>().<span class="built_in">c_str</span>());</span><br><span class="line"> <span class="function">FileSplitter <span class="title">splitter</span><span class="params">(filePath , number , <span class="keyword">this</span>)</span></span>;</span><br><span class="line"> splitter.<span class="built_in">split</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="type">void</span> <span class="title">DoProgress</span><span class="params">(<span class="type">float</span> value)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> precessBar-><span class="built_in">setValue</span>(value);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>定义对象之间的一种一对多(变化)的依赖关系,以便当一个对象(subject)的状态发生改变时,所有依赖它的对象都得到通知并自动更新</p><p><img src="/img/Snipaste_2024-12-16_16-35-00.png"></p><ul><li>使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使两者之间的依赖关系达到松耦合</li><li>目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播</li><li>观察者自己决定是否需要订阅通知,目标对象一无所知</li></ul><h2 id="单一职责模式"><a href="#单一职责模式" class="headerlink" title="单一职责模式"></a>单一职责模式</h2><blockquote><p>在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任</p><p>典型模式</p><ul><li><p>Decorator</p></li><li><p>Bridge</p></li></ul></blockquote><h3 id="装饰模式"><a href="#装饰模式" class="headerlink" title="装饰模式"></a>装饰模式</h3><blockquote><p>Motivation</p></blockquote><p>在某些情况下我们可能会过度的使用继承来拓展对象的功能,由于继承为类型引入静态特质,使得这种拓展方式缺乏灵活性,并随着子类的增多(拓展功能的增多),各种子类的组合(拓展功能的组合)会导致更多子类的膨胀。如何使对象功能的拓展能够根据需要来动态地实现?同时避免拓展功能的增多带来的子类膨胀问题?从而使得任何功能拓展变化导致的影响降到最低</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Stream</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Stream</span>() {};</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FileStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NetStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</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_">CryptoFileStream</span> : <span class="keyword">public</span> FileStream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Seek</span>(position);</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Wirte</span>(data);</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_">CryptoNetStream</span> : <span class="keyword">public</span> NetStream {}; <span class="comment">// 网络流加密</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BufferFileStream</span> : <span class="keyword">public</span> FileStream {}; <span class="comment">// 文件流缓冲</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BufferNetStream</span> : <span class="keyword">public</span> NetStream {}; <span class="comment">// 网络流缓冲</span></span><br><span class="line"><span class="comment">// 文件流即加密又缓冲</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CryptoBufferFileStream</span> : <span class="keyword">public</span> FileStream {}<span class="comment">// 业务操作</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Stream</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Stream</span>() {};</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FileStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NetStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</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_">CryptoFileStream</span> : <span class="keyword">public</span> FileStream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Seek</span>(position);</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> FileStream::<span class="built_in">Wirte</span>(data);</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_">CryptoNetStream</span> : <span class="keyword">public</span> NetStream {}; <span class="comment">// 网络流加密</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BufferFileStream</span> : <span class="keyword">public</span> FileStream {}; <span class="comment">// 文件流缓冲</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BufferNetStream</span> : <span class="keyword">public</span> NetStream {}; <span class="comment">// 网络流缓冲</span></span><br><span class="line"><span class="comment">// 文件流即加密又缓冲</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CryptoBufferFileStream</span> : <span class="keyword">public</span> FileStream {}</span><br></pre></td></tr></table></figure><p><img src="/img/Snipaste_2024-12-20_20-10-18.png"></p><p>此图是如上代码的继承关系,我们有太多的子类,而且存在过多的重复代码,如各个子类的加密操作,我们进行重构</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Stream</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Stream</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_">FileStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NetStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </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_">DecoratorStream</span> : <span class="keyword">public</span> Stream { <span class="comment">// 装饰类</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> Stream* stream;</span><br><span class="line"> <span class="built_in">DecoratorStream</span>(Stream* stm) : <span class="built_in">stream</span>(stm) {}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CryptoStream</span> : <span class="keyword">public</span> Stream { <span class="comment">//任然为流保持接口规范</span></span><br><span class="line"> Stream* stream; <span class="comment">// FileStream NetStream</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CryptoStream</span>(Stream* stm) : <span class="built_in">stream</span>(stm) {}</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Seek</span>(position);</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Wirte</span>(data);</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_">BufferStream</span> : <span class="keyword">public</span> Stream { <span class="comment">// 文件流缓冲</span></span><br><span class="line"> Stream* stream;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">BufferStream</span>(Stream* stm) : <span class="built_in">stream</span>(stm) {}</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Seek</span>(position);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Wirte</span>(data);</span><br><span class="line"> }</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_">CryptoBufferStream</span> : <span class="keyword">public</span> Stream {...};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{</span><br><span class="line"> FileStream* s1 = <span class="keyword">new</span> <span class="built_in">FileStream</span>();</span><br><span class="line"> CryptoStream* s2 = <span class="keyword">new</span> <span class="built_in">CryptoStream</span>(s1); <span class="comment">//加密文件流</span></span><br><span class="line"> BufferStream* s3 = <span class="keyword">new</span> <span class="built_in">BufferStream</span>(s1); <span class="comment">//加密缓冲流</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上已经满足需求了,当所有子类中拥有相同的成员时,我们可以将他提到基类中,显然提到Stream中是不合理的,所以我们也可以引入了装饰类</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Stream</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Stream</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_">FileStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{ }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NetStream</span> : <span class="keyword">public</span> Stream {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{ }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </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_">DecoratorStream</span> : <span class="keyword">public</span> Stream { <span class="comment">// 装饰类</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> Stream* stream;</span><br><span class="line"> <span class="built_in">DecoratorStream</span>(Stream* stm) : <span class="built_in">stream</span>(stm) {}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CryptoStream</span> : <span class="keyword">public</span> DecoratorStream{ <span class="comment">//任然为流保持接口规范</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CryptoStream</span>(Stream* stm) : <span class="built_in">DecoratorStream</span>(stm) {}</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Seek</span>(position);</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{</span><br><span class="line"> <span class="comment">// 加密操作</span></span><br><span class="line"> stream-><span class="built_in">Wirte</span>(data);</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_">BufferStream</span> : <span class="keyword">public</span> DecoratorStream{ <span class="comment">// 文件流缓冲</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">BufferStream</span>(Stream* stm) : <span class="built_in">DecoratorStream</span>(stm) {}</span><br><span class="line"> <span class="function"><span class="type">char</span> <span class="title">Read</span><span class="params">(<span class="type">int</span> number)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Read</span>(number);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Seek</span><span class="params">(<span class="type">int</span> position)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Seek</span>(position);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Wirte</span><span class="params">(<span class="type">char</span> data)</span> </span>{</span><br><span class="line"> <span class="comment">// 缓冲操作</span></span><br><span class="line"> stream-><span class="built_in">Wirte</span>(data);</span><br><span class="line"> }</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_">CryptoBufferStream</span> : <span class="keyword">public</span> DecoratorStream{};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{</span><br><span class="line"> FileStream* s1 = <span class="keyword">new</span> <span class="built_in">FileStream</span>();</span><br><span class="line"> CryptoStream* s2 = <span class="keyword">new</span> <span class="built_in">CryptoStream</span>(s1); <span class="comment">//加密文件流</span></span><br><span class="line"> BufferStream* s3 = <span class="keyword">new</span> <span class="built_in">BufferStream</span>(s1); <span class="comment">//加密缓冲流</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="/img/Snipaste_2024-12-24_13-14-42.png"></p><p>我们引入了<code>DecoratorStream</code>类,用于拓展操作,拓展的类依赖<code>Stream</code>的派生类</p><blockquote><p>define</p></blockquote><p>动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)</p><p><img src="/img/Snipaste_2024-12-24_13-31-45.png"></p><ul><li><p>通过采用组合而非继承的首发,Decorator模式实现来运行时动态拓展对象功能的能力,而且可以根据需要拓展多个功能。避免来使用继承带来的灵活性差和多子类衍生问题</p></li><li><p>Decorator类在接口上表现为<strong>is-a Component</strong>继承关系,即<code>Decorator</code>类继承了<code>Component</code>类所拥有的接口。但实现上又变现为<strong>has-a Component</strong>的组合关系,即<code>Decorator</code>类又使用来另外一个<code>Component</code>类</p></li><li><p>Decorator模式的目的并非解决多子类衍生的多继承问题,Decorator模式应用的要点在于解决主体类在多个方向上的拓展功能 – “装饰”的含义</p></li></ul><h3 id="桥模式"><a href="#桥模式" class="headerlink" title="桥模式"></a>桥模式</h3><blockquote><p>Motivation</p></blockquote><p>由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度变化。如何应对这种”多维度的变化”? 如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Messageer</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Messageer</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_">PCMessagerBase</span> : <span class="keyword">public</span> Messageer {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> <span class="keyword">override</span> </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_">MobileMessagerBase</span> : <span class="keyword">public</span> Messageer {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> <span class="keyword">override</span> </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_">PCMessagerLite</span> : <span class="keyword">public</span> PCMessagerBase { <span class="comment">// pc平台精简版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">Connect</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">DrawShape</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><br><span class="line"><span class="keyword">class</span> <span class="title class_">PCMessagerPerfect</span> : <span class="keyword">public</span> PCMessagerBase { <span class="comment">// pc平台完美版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> PCMessagerBase::<span class="built_in">Connect</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> PCMessagerBase::<span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> PCMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> PCMessagerBase::<span class="built_in">DrawShape</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><br><span class="line"><span class="keyword">class</span> <span class="title class_">MobileMessagerPerfect</span> : <span class="keyword">public</span> MobileMessagerBase { <span class="comment">// mobile平台精简版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> MobileMessagerBase::<span class="built_in">Connect</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> MobileMessagerBase::<span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">PlaySound</span>();</span><br><span class="line"> MobileMessagerBase::<span class="built_in">DrawShape</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><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MobileMessagerLite</span> : <span class="keyword">public</span> PCMessagerBase { <span class="comment">// mobile平台精简版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">Connect</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> MobileMessagerBase::<span class="built_in">DrawShape</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>假设我们的业务抽象为m平台实现为n,那我们类的数目就有 m * n 个,我们发现PcMessagerLite和MobileMessagerLite中存在大量的重复代码对应的perfect中也同样存在。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Messager</span> {</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> MessagerImpl* messageImpl; <span class="comment">//运行时确定是pcMessagerBase or mobileMessagerBase</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Messager</span>(MessagerImpl* impl) : <span class="built_in">messageImpl</span>(impl) {}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Messager</span>() = <span class="keyword">default</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_">MessagerImpl</span> {</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">MessagerImpl</span>() = <span class="keyword">default</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_">PCMessagerBase</span> : <span class="keyword">public</span> MessagerImpl {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> <span class="keyword">override</span> </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_">MobileMessagerBase</span> : <span class="keyword">public</span> MessagerImpl {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">PlaySound</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">DrawShape</span><span class="params">()</span> <span class="keyword">override</span> </span>{}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">WriteText</span><span class="params">()</span> <span class="keyword">override</span> </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_">MessagerLite</span> : Messager { <span class="comment">// 精简版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">MessagerLite</span>(MessagerImpl* impl) : <span class="built_in">Messager</span>(impl) {}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> messageImpl-><span class="built_in">Connect</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> message-><span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> messageImpl-><span class="built_in">DrawShape</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><br><span class="line"><span class="keyword">class</span> <span class="title class_">MessagerPerfect</span> : Messager { <span class="comment">// 完美版</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">MessagerPerfect</span>(MessagerImpl* impl) : <span class="built_in">Messager</span>(impl) {}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Login</span><span class="params">(string username , string password)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> message-><span class="built_in">PlaySound</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> message-><span class="built_in">Connect</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendMessage</span><span class="params">(string message)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> message-><span class="built_in">PlaySound</span>();</span><br><span class="line"> message-><span class="built_in">WriteText</span>();</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">SendPicture</span><span class="params">(Image image)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> message-><span class="built_in">PlaySound</span>();</span><br><span class="line"> message-><span class="built_in">DrawShape</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><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{</span><br><span class="line"> MessagerImpl* impl = <span class="keyword">new</span> PCMessagerBase;</span><br><span class="line"> Messager* message = <span class="keyword">new</span> <span class="built_in">MessagerLite</span>(impl);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>将抽象部分(业务功能)与实现部分(平台实现)分离,使他们可以独立的变化</p><p><img src="/img/Snipaste_2024-12-27_15-42-55.png"></p><ul><li><p>Bridge 模式使用对象间的组合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各个维度的变化即子类化他们</p></li><li><p>Bridge模式有时类似于多继承方案,但是多继承方案往往违背单一职责模式(一个类只有一个变化的原因),复用型比较差。Bridge模式是比多继承方案更好的解决方法</p></li><li><p>Bridge模式的应用一般在两个非常强的变化维度,有时一个类也有多于两个的变化维度,这时可以使用Bridge的拓展模式</p></li></ul><h2 id="对象创建模式"><a href="#对象创建模式" class="headerlink" title="对象创建模式"></a>对象创建模式</h2><blockquote><p>通过对象创建模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作</p><p>典型模式</p><ul><li><p>Factor Method</p></li><li><p>Abstract Factor</p></li><li><p>Prototype</p></li><li><p>Builder</p></li></ul></blockquote><h3 id="工厂方法"><a href="#工厂方法" class="headerlink" title="工厂方法"></a>工厂方法</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化。如何应对这种变化?如何绕开常规的对象创建方法(new),提供一种封装机制来避免客户程序和这种具体对象创建工作的紧耦合?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Form</span> {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ISplitter</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">split</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">ISplitter</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_">FileSplitter</span> : ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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_">BinarySplitter</span> : ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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_">TxtSplitter</span> : ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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_">VideoSplitter</span> : ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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><br><span class="line"><span class="keyword">class</span> <span class="title class_">MainForm</span> : <span class="keyword">public</span> Form {</span><br><span class="line"> TextBox* txtFilePath;</span><br><span class="line"> TextBox* txtFileNumber;</span><br><span class="line"> ProgressBar* processBar;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Button1_Click</span><span class="params">()</span> </span>{</span><br><span class="line"> string filePath = txtFilePath-><span class="built_in">toString</span>();</span><br><span class="line"> <span class="type">int</span> number = <span class="built_in">atoi</span>(txtFileNumber-><span class="built_in">toString</span>());</span><br><span class="line"> ISplitter* splitter = <span class="keyword">new</span> <span class="built_in">FileSplitter</span>(filePath , number); <span class="comment">// 依赖实现细节(具体类)</span></span><br><span class="line"> splitter-><span class="built_in">split</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>上述代码中我们抽象了接口<code>ISplitter</code>,但是我们在MainForm中任然使用了实现细节<code>FileSplitter</code>,这违反了依赖倒置原则</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ISplitter</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">split</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">ISplitter</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_">SplitterFactory</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 接口:工厂创建子类的方法,Factory Method 名字的由来</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> ISplitter* <span class="title">CreateSplitter</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">SplitterFactory</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_">FileSplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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_">BinarySplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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_">TxtSplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </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><br><span class="line"><span class="keyword">class</span> <span class="title class_">FileSplitterFactory</span> : SplitterFactory{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">ISplitter* <span class="title">CreateSplitter</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> FileSplitter;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BinarySplitterFactory</span> : SplitterFactory{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">ISplitter* <span class="title">CreateSplitter</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BinarySplitter;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TxtSplitterFactory</span> : SplitterFactory{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">ISplitter* <span class="title">CreateSplitter</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> BinarySplitter;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MainForm</span> : <span class="keyword">public</span> Form {</span><br><span class="line"> SplitterFactory* factory;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">MainForm</span>(SplitterFactory* factory) { <span class="comment">// 依赖注入</span></span><br><span class="line"> <span class="keyword">this</span>->factory =factory;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Button1_Click</span><span class="params">()</span> </span>{</span><br><span class="line"> ISplitter* splitter = factory-><span class="built_in">CreateSplitter</span>(); <span class="comment">// 称为多肽new</span></span><br><span class="line"> splitter-><span class="built_in">split</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>经过修改后,MainForm只依赖抽象基类而不依赖具体实现,我们将变化的依赖赶到了MainForm的构造函数中</p><blockquote><p>define</p></blockquote><p>定义一个用于创建对象的接口,让子类决定实例化那一个类。<code>Factory Method</code> 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类</p><p><img src="/img/Snipaste_2024-12-28_13-20-38.png"></p><ul><li><p>Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱</p></li><li><p>Factory Method 模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种拓展(而非更改)的策略,叫好的解决了这种紧耦合关系</p></li><li><p>Factory Method模式解决单个对象的需求变化。缺点在于要求创建方法参数相同</p></li></ul><h3 id="抽象工厂"><a href="#抽象工厂" class="headerlink" title="抽象工厂"></a>抽象工厂</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中,经常面临一系列相互依赖的对象的创建工作,同时,由于需求的变化,往往存在很多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一中封装机制来避免客户程序和这种多系列具体对象创建工作的紧耦合?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">EmployeeDAO</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<EmployeeD0> <span class="title">GetEmployees</span><span class="params">()</span> </span>{</span><br><span class="line"> SqlConnection* connection = <span class="keyword">new</span> <span class="built_in">SqlConnection</span>();</span><br><span class="line"> connection->ConnectionSring = <span class="string">"..."</span>;</span><br><span class="line"> SqlCommand* command = <span class="keyword">new</span> <span class="built_in">SqlCommand</span>();</span><br><span class="line"> command->CommandText = <span class="string">"..."</span>;</span><br><span class="line"> SqlDataReader* reader = command-><span class="built_in">ExecuteReader</span>();</span><br><span class="line"> <span class="keyword">while</span>(reader-><span class="built_in">Read</span>()) {</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>如上代码数据库已经和sqlServer绑定死了,不适用于多种数据库的变化</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数据库访问有关的基类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBConnection</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBCommand</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBDataReader</span> {};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 支持SqlServer</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlConnection</span> : <span class="keyword">public</span> IDBConnection {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlCommand</span> : <span class="keyword">public</span> IDBCommand {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlDataReader</span> : <span class="keyword">public</span> IDataReader {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 支持Oracle</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleConnection</span> : <span class="keyword">public</span> IDBConnection {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleCommand</span> : <span class="keyword">public</span> IDBCommand {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleDataReader</span> : <span class="keyword">public</span> IDataReader {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EmployeeDAO</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<EmployeeD0> <span class="title">GetEmployees</span><span class="params">()</span> </span>{</span><br><span class="line"> IDBConnection* connection = <span class="keyword">new</span> <span class="built_in">SqlConnection</span>();</span><br><span class="line"> connection->ConnectionSring = <span class="string">"..."</span>;</span><br><span class="line"> IDBCommand* command = <span class="keyword">new</span> <span class="built_in">SqlCommand</span>();</span><br><span class="line"> command->CommandText = <span class="string">"..."</span>;</span><br><span class="line"> command-><span class="built_in">SetConnection</span>(connection);</span><br><span class="line"> IDBDataReader* reader = command-><span class="built_in">ExecuteReader</span>();</span><br><span class="line"> <span class="keyword">while</span>(reader-><span class="built_in">Read</span>()) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>为了支持多种数据库我们定义来接口,用多态的方式完成,由于需要new 子类所以任然不满足依赖倒置。我们很容易想到用工厂方法进行改良</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">IDBConnection</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBCommand</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBDataReader</span> {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBConnectionFactory</span> { <span class="comment">/*virtual create method*/</span>};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBCommandFactory</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBDataReaderFactory</span> {};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 支持SqlServer</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlConnection</span> : <span class="keyword">public</span> IDBConnection {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlConnectionFactory</span> : IDBConnectionFactory {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlCommand</span> : <span class="keyword">public</span> IDBCommand {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlCommandFactory</span> : IDBCommandFactory {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlDataReader</span> : <span class="keyword">public</span> IDataReader {</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>我们有三个接口,所以我们会有三个工厂接口<code>IDBConnectionFactory IDBCommandFactory IDBDataReaderFactory</code>,要实现<code>SqlConnectionFactory</code>用于创建<code>SqlConnection</code>,其他类如法炮制,在EmployeeDAO类中,要存放三个工厂接口的指针。由于Connection Command DataReader 之间存在关联性,必须操作同一类数据库。为了避免用户传参导致三者混乱我们有如下优化方法</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">IDBConnection</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBCommand</span> {};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBDataReader</span> {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">IDBFactory</span> { <span class="comment">// 将三个工厂合为一个工厂</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBConnection* <span class="title">CreateDBConnection</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBCommand* <span class="title">CreateDBCommand</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBDataReader* <span class="title">CreateDBDataReader</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 支持SqlServer</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlConnection</span> : <span class="keyword">public</span> IDBConnection {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlCommand</span> : <span class="keyword">public</span> IDBCommand {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlDataReader</span> : <span class="keyword">public</span> IDataReader {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SqlFactory</span> : <span class="keyword">public</span> IDBFactory {</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBConnection *<span class="title">CreateDBConnection</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">new</span> SqlConnection; }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBCommand *<span class="title">CreateDBCommand</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">new</span> SqlCommand; }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> IDBDataReader *<span class="title">CreateDBDataReader</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="keyword">new</span> SqlDataReader; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 支持Oracle</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleConnection</span> : <span class="keyword">public</span> IDBConnection {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleCommand</span> : <span class="keyword">public</span> IDBCommand {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OracleDataReader</span> : <span class="keyword">public</span> IDataReader {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EmployeeDAO</span> {</span><br><span class="line"> IDBFactory* DBfactory; <span class="comment">// 构造函数,依赖注入</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<EmployeeD0> <span class="title">GetEmployees</span><span class="params">()</span> </span>{</span><br><span class="line"> IDBConnection* connection = DBfactory-><span class="built_in">CreateDBConnection</span>();</span><br><span class="line"> connection->ConnectionSring = <span class="string">"..."</span>;</span><br><span class="line"> IDBCommand* command = DBfactory-><span class="built_in">CreateDBCommand</span>();</span><br><span class="line"> command->CommandText = <span class="string">"..."</span>;</span><br><span class="line"> command-><span class="built_in">SetConnection</span>(connection);</span><br><span class="line"> IDBDataReader* reader = DBfactory-><span class="built_in">CreateDBDataReader</span>();</span><br><span class="line"> <span class="keyword">while</span>(reader-><span class="built_in">Read</span>()) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>由于connection command datareader之间存在关联性,我们把原来的三个工厂合为了一个工厂</p><blockquote><p>define</p></blockquote><p>提供一个接口(工厂),让该接口负责创建一系列相关或者相互依赖的对象(多个工厂函数),无需指定他们具体的类</p><p><img src="/img/Snipaste_2024-12-29_18-13-38.png"></p><ul><li><p>如果没有应对多系列对象构建的需求变化,则没有必要使用Abstract Factory模式,这时候简单工厂完全可以</p></li><li><p>系列对象指的是在某一特定系列下的对象之间有相互依赖或做用关系。不同系列的对象之间不能相互依赖</p></li><li><p>Abstract Factory模式主要在于应对新系列的需求变动。其缺点在难以应对新对象的需求变动</p></li></ul><h3 id="原型模式"><a href="#原型模式" class="headerlink" title="原型模式"></a>原型模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中,经常面临着某些<strong>结构复杂</strong>的对象的创建工作,由于需求的变化,这些对象经常面临剧烈的变化,但是他们却拥有比较稳定一致的接口。如何对应这种变化?如何向客户程序(使用这些对象的程序)隔离出这些易变对象,从而使得这些易变对象的客户程序不随着需求改变而变化?</p><p>我们对FactoryMethod的代码进行修改</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Form</span> {};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ISplitter</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">split</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> ISplitter* <span class="title">clone</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">// 通过clone自己来创建对象</span></span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">ISplitter</span>() = <span class="keyword">default</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_">FileSplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function">ISplitter* <span class="title">clone</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">FileSplitter</span>(*<span class="keyword">this</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_">BinarySplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function">ISplitter* <span class="title">clone</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">BinarySplitter</span>(*<span class="keyword">this</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_">TxtSplitter</span> : <span class="keyword">public</span> ISplitter {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">split</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function">ISplitter* <span class="title">clone</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">TxtSplitter</span>(*<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MainForm</span> : <span class="keyword">public</span> Form {</span><br><span class="line"> ISplitter* prototype; <span class="comment">// 用于clone,而不是直接使用</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">MainForm</span>(ISplitter* prototype) { <span class="comment">// 依赖注入</span></span><br><span class="line"> <span class="keyword">this</span>->prototype=prototype;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Button1_Click</span><span class="params">()</span> </span>{</span><br><span class="line"> ISplitter* splitter = prototype-><span class="built_in">clone</span>(); <span class="comment">// 称为多肽new</span></span><br><span class="line"> splitter-><span class="built_in">split</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>原型模式将抽象类和工厂基类进行了合并</p><blockquote><p>define</p></blockquote><p>使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象</p><p><img src="/img/Snipaste_2024-12-30_13-03-16.png"></p><ul><li><p>Prototype模式同样用于隔离对象的使用者和具体类型(易变)之间的耦合关系,它同样要求这些易变类拥有稳定的接口</p></li><li><p>Prototype模式对于如何创建易变类的实体对象采用原型克隆方法来做,它使得我们可以非常灵活的动态的创建拥有某些稳定接口的新对象(所需工作仅仅是注册一个新类的对象即原型然后在任何需要的地方使用clone)</p></li><li><p>Prototype模式中的clone方法可以利用某些框架中的序列化来实现深拷贝</p></li></ul><h2 id="对象性能模式"><a href="#对象性能模式" class="headerlink" title="对象性能模式"></a>对象性能模式</h2><blockquote><p>面向对象很好的解决了抽象的问题,但是必不可免的要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理</p><p>典型模式</p><ul><li><p>Singleton</p></li><li><p>Flywight</p></li></ul></blockquote><h3 id="单件模式"><a href="#单件模式" class="headerlink" title="单件模式"></a>单件模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这应该是类设计者的职责,而不是使用者的责任</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><atomic></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><cstddef></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><mutex></span></span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span> {</span><br><span class="line"> <span class="built_in">Singleton</span>();</span><br><span class="line"> <span class="built_in">Singleton</span>(<span class="type">const</span> Singleton&) = <span class="keyword">delete</span>;</span><br><span class="line"> Singleton& <span class="keyword">operator</span>=(<span class="type">const</span> Singleton&) = <span class="keyword">delete</span>;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> Singleton* <span class="title">getInstance</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="type">static</span> Singleton* m_instance;</span><br><span class="line">};</span><br><span class="line">Singleton* Singleton::m_instance = <span class="literal">nullptr</span>;</span><br><span class="line"><span class="comment">//非线程安全</span></span><br><span class="line"><span class="function">Singleton* <span class="title">Singleton::getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> <span class="built_in">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> m_instance;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//线程安全,但锁消耗过大,当对象已经创建</span></span><br><span class="line"><span class="comment">//再次调用getInstance属于读m_instance加锁无意义</span></span><br><span class="line"><span class="function">Singleton* <span class="title">Singleton::getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> Lock lock;</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> <span class="built_in">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> m_instance;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//双检查锁,但由于内存读写reorder不安全</span></span><br><span class="line"><span class="function">Singleton* <span class="title">Singleton::getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) { <span class="comment">// 1</span></span><br><span class="line"> Lock lock;</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> <span class="built_in">Singleton</span>(); <span class="comment">// 2</span></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 1. 分配内存</span></span><br><span class="line"><span class="comment"> 2. 调用构造器</span></span><br><span class="line"><span class="comment"> 3. 将地址返回</span></span><br><span class="line"><span class="comment"> 在reorder后指令顺序可能变为</span></span><br><span class="line"><span class="comment"> 1. 分配内存</span></span><br><span class="line"><span class="comment"> 2. 将地址返回</span></span><br><span class="line"><span class="comment"> 3. 调用构造器</span></span><br><span class="line"><span class="comment"> 在经历指令重排后,线程一执行了1 2 步骤直接将地址返回</span></span><br><span class="line"><span class="comment"> 线程二进入getInstance函数,发现m_instance不为空直接返回</span></span><br><span class="line"><span class="comment"> m_instance,但是此时m_instance所指地址并没有调用构造器</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">return</span> m_instance;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//c++11版本之后的跨平台实现</span></span><br><span class="line">std::atomic<Singleton*> Singleton::m_instance;</span><br><span class="line">std::mutex Singleton::m_mutex;</span><br><span class="line"></span><br><span class="line"><span class="function">Singleton* <span class="title">Singleton::getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> Singleton* tmp = m_instance.<span class="built_in">load</span>(std::memory_order_relaxed);</span><br><span class="line"> std::<span class="built_in">atomic_thread_fence</span>(std::memory_order_acquire);</span><br><span class="line"> <span class="keyword">if</span>(tmp == <span class="literal">nullptr</span>) {</span><br><span class="line"> <span class="function">std::lock_guard<std::mutex> <span class="title">lock</span><span class="params">(m_mutex)</span></span>;</span><br><span class="line"> tmp = m_instance.<span class="built_in">load</span>(std::memory_order_relaxed);</span><br><span class="line"> <span class="keyword">if</span>(tmp == <span class="literal">nullptr</span>) {</span><br><span class="line"> tmp = <span class="keyword">new</span> Singleton;</span><br><span class="line"> std::<span class="built_in">atomic_thread_fence</span>(std::memory_order_release);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>保证一个类仅有一个实例,并提供一个该实例的全局访问点</p><p><img src="/img/Snipaste_2025-01-05_13-17-19.png"></p><ul><li><p>Singleton 模式中的实例构造器可以设为为protected以允许子类派生</p></li><li><p>Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背</p></li></ul><h3 id="享元模式"><a href="#享元模式" class="headerlink" title="享元模式"></a>享元模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价(主要指内存需求方面的代价)。如何避免大量细粒度对象问题的同时,让外部客户程序任然能够透明的使用面向对象的方式来进行操作?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Font</span> {</span><br><span class="line"> string key;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Font</span>(<span class="type">const</span> string& key) {</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="keyword">class</span> <span class="title class_">FontFactory</span> {</span><br><span class="line"> map<string , Font*> fontPool;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Font* <span class="title">GetFont</span><span class="params">(<span class="type">const</span> string& key)</span> </span>{</span><br><span class="line"> map<string , Font*>::iterator item = fontPool.<span class="built_in">find</span>(key);</span><br><span class="line"> <span class="keyword">if</span>(item != fontPool.<span class="built_in">end</span>()) {</span><br><span class="line"> <span class="keyword">return</span> footPool[key];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Font* font = <span class="keyword">new</span> <span class="built_in">Font</span>(key);</span><br><span class="line"> fontPool[key] = font;</span><br><span class="line"> <span class="keyword">return</span> font;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>运用共享技术有效的支持大量细粒度的对象</p><ul><li><p>面向对象很好的解决了抽象性问题,但是作为一个运行在机器中程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题</p></li><li><p>Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理</p></li><li><p>对象的数量太大从而导致对象埃格努内存开销加大,什么样的数量才算大?这需要我们仔细根据具体应用情况进行评估,而不能凭空臆断</p></li></ul><h2 id="接口隔离模式"><a href="#接口隔离模式" class="headerlink" title="接口隔离模式"></a>接口隔离模式</h2><blockquote><p>在组件构架过程中,某些接口之间直接的依赖常常会带来很多问题,甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案</p><p>典型模式</p><ul><li><p>Facade</p></li><li><p>Proxy</p></li><li><p>Adapter</p></li><li><p>Mediator</p></li></ul></blockquote><h3 id="门面模式"><a href="#门面模式" class="headerlink" title="门面模式"></a>门面模式</h3><p><img src="/img/Snipaste_2025-01-08_10-59-52.png"></p><blockquote><p>Motivation</p></blockquote><p>上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。如何简化外部客户程序和系统间交互的接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?</p><blockquote><p>define</p></blockquote><p>为子系统中的一组接口提供一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用</p><ul><li><p>从客户程序的角度看,Facade模式简化来整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种解耦的效果,内部子系统的任何变化不用影响到Facade接口的变化</p></li><li><p>Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式</p></li><li><p>Facade设计模式并非一个集装箱,可以任意的放进任何多个对象。Facade模式中组件的内部应该是相互耦合关系比较大的一系列组件,而不是一个简单的功能集合</p></li></ul><h3 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h3><blockquote><p>Motivation</p></blockquote><p>在面向对象系统中,有些对象由于某种原因(比如对象创建的开销大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。如何在不时去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ISubject</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>= <span class="number">0</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_">RealSubject</span> : <span class="keyword">public</span> ISubject {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{<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_">ClientApp</span> {</span><br><span class="line"> ISubject* subject;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ClientApp</span>() {</span><br><span class="line"> subject = <span class="keyword">new</span> RealSubject;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">DoTask</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> subject-><span class="built_in">process</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><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ISubject</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>= <span class="number">0</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_">SubjectProxy</span> : <span class="keyword">public</span> ISubject {</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 对RealSubject的间接访问</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="keyword">class</span> <span class="title class_">ClientApp</span> {</span><br><span class="line"> ISubject* subject;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ClientApp</span>() {</span><br><span class="line"> subject = <span class="keyword">new</span> SubjectProxy;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">DoTask</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> subject-><span class="built_in">process</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><blockquote><p>define</p></blockquote><p>为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。</p><p><img src="/img/Snipaste_2025-01-09_10-24-03.png"></p><ul><li><p>增加一层间接层是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是这一问题的常用手段</p></li><li><p>具体proxy设计模式的实现方法,实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy</p></li><li><p>Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损失一些透明性是可以接受的</p></li></ul><h3 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件系统中,由于应用环境的变化,常常需要将一些现存的对象放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。如何应对这种迁移的变化?如何即能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ITarget</span> { <span class="comment">// 目标接口(新接口)</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>= <span class="number">0</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_">IAdaptee</span> { <span class="comment">// 遗留接口(老接口)</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> data)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">int</span> <span class="title">bar</span><span class="params">()</span> </span>= <span class="number">0</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_">Adapter</span> : <span class="keyword">public</span> ITarget { <span class="comment">// 适配器</span></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> IAdaptee* pAdaptee; <span class="comment">// 组合了老接口</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="type">int</span> data = pAdaptee-><span class="built_in">bar</span>();</span><br><span class="line"> pAdaptee-><span class="built_in">foo</span>(<span class="number">2</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><blockquote><p>define</p></blockquote><p>将一个类的接口转换为客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作</p><p><img src="/img/Snipaste_2025-01-10_11-15-05.png"></p><ul><li><p>Adapter模式主要应用于希望复用一些现纯的类,但是接口又与复用环境要求不一致的情况,在遗留代码复用 类库迁移等方面非常有用</p></li><li><p>GOF23定义了两种Adapter模式的实现结构,对象适配器和类适配器。但类适配器采用多继承的实现方法,一般不推荐使用。对象适配器采用对象组合的方式,更符合松耦合精神</p></li><li><p>Adapter模式可以实现的非常灵活,不必拘泥与GOF23中定义的两种结构。例如,完全可以将Adapter模式中现纯对象作为新的接口方法参数,来达到适配的目的</p></li></ul><h3 id="中介者模式"><a href="#中介者模式" class="headerlink" title="中介者模式"></a>中介者模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件构建过程中,经常会出现多个对象相互关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。这种情况下,我们可以使用一个中介对象来管理对象间的关联关系,避免相互交互对象之间的紧耦合引用关系,从而更好的抵御变化。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Colleague</span>;</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_">Mediator</span> {</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Mediator</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">changed</span><span class="params">(Colleague *)</span></span>=<span class="number">0</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_">Colleague</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"> <span class="comment">//初始化中介者类对象</span></span><br><span class="line"> <span class="built_in">Colleague</span>(Mediator *mediator) { <span class="keyword">this</span>->mediator = mediator; }</span><br><span class="line"> <span class="comment">// 更新另外一个类</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">changed</span><span class="params">()</span> </span>{ mediator-><span class="built_in">changed</span>(<span class="keyword">this</span>); }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> Mediator *mediator;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//具体的同事类1</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteColleague1</span> :<span class="keyword">public</span> Colleague</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConcreteColleague1</span>(Mediator *mediator) : <span class="built_in">Colleague</span>(mediator) {}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"update ConcreteColleague1 from ConcreteColleague2"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">//具体的同事类2</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteColleague2</span> :<span class="keyword">public</span> Colleague</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"> <span class="built_in">ConcreteColleague2</span>(Mediator *mediator) : <span class="built_in">Colleague</span>(mediator) {}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">update</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"update ConcreteColleague2 from ConcreteColleague1"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//具体的中介者类,实现更新函数changed。</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteMediator</span> :<span class="keyword">public</span> Mediator {</span><br><span class="line"> ConcreteColleague1 * colleague1;</span><br><span class="line"> ConcreteColleague2 * colleague2;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setColleague1</span><span class="params">(ConcreteColleague1 *colleague)</span> </span>{ colleague1 = colleague; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setColleague2</span><span class="params">(ConcreteColleague2 *colleague)</span> </span>{ colleague2 = colleague; }</span><br><span class="line"> <span class="built_in">ConcreteMediator</span>() {</span><br><span class="line"> <span class="comment">// colleague1 = new ConcreteColleague1(this);</span></span><br><span class="line"> }</span><br><span class="line"> ~<span class="built_in">ConcreteMediator</span>() {}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">changed</span><span class="params">(Colleague *colleague)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (colleague == colleague1) {</span><br><span class="line"> colleague2-><span class="built_in">update</span>();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (colleague == colleague2) {</span><br><span class="line"> colleague1-><span class="built_in">update</span>();</span><br><span class="line"> } <span class="keyword">else</span></span><br><span class="line"> ;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ConcreteMediator concreteMediator;</span><br><span class="line"> <span class="function">ConcreteColleague1 <span class="title">colleague1</span><span class="params">(&concreteMediator)</span></span>;</span><br><span class="line"> <span class="function">ConcreteColleague2 <span class="title">colleague2</span><span class="params">(&concreteMediator)</span></span>;</span><br><span class="line"> concreteMediator.<span class="built_in">setColleague1</span>(&colleague1);</span><br><span class="line"> concreteMediator.<span class="built_in">setColleague2</span>(&colleague2);</span><br><span class="line"> <span class="comment">//"同事1"通过中介者更新"同事2"</span></span><br><span class="line"> colleague1.<span class="built_in">changed</span>();</span><br><span class="line"> <span class="comment">//"同事2"通过中介者更新"同事1"</span></span><br><span class="line"> colleague2.<span class="built_in">changed</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖),从而使其耦合松散,而且可以独立的改变他们之间的交互</p><p><img src="/img/Snipaste_2025-01-11_12-59-19.png"></p><ul><li><p>将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变多个对象相互关联为多个对象和一个中介者关联,简化来系统的维护,抵御来可能的变化</p></li><li><p>随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理</p></li><li><p>Facade模式是解耦系统间(单向)的对象关联关系,Mediator模式是解耦系统内各个对象之间(双向)的关联关系</p></li></ul><p>、</p><h2 id="状态变化模式"><a href="#状态变化模式" class="headerlink" title="状态变化模式"></a>状态变化模式</h2><blockquote><p>在组件构建过程中,某些对象的状态经常面临变化,如何对这种变化进行有效的管理?同时又维持高层模块的稳定?状态变化模式为这一问题提供了一种解决方案。</p><p>典型模式:</p><ul><li><p>State</p></li><li><p>Memento</p></li></ul></blockquote><h3 id="状态模式"><a href="#状态模式" class="headerlink" title="状态模式"></a>状态模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件构建过程中,某些对象的状态如果改变,其行为也会随之发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。如何在运行时根据对象的状态来透明的更改对象的行为?而不会为对象操作和状态转换之间引入紧耦合。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">NetWorkState</span> {</span><br><span class="line"> Network_Open,</span><br><span class="line"> Network_Close,</span><br><span class="line"> Network_Connect,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NetworkProcessor</span> {</span><br><span class="line"> NetWorkState state;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(state == NetWorkState::Network_Open) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> state = NetWorkState::Network_Close;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(state == NetWorkState::Network_Close) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> state = NetWorkState::Network_Connect;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(state == NetWorkState::Network_Connect) {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> state = NetWorkState::Network_Open;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(state == NetWorkState::Network_Open) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> state = NetWorkState::Network_Connect;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(state == NetWorkState::Network_Close) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> state = NetWorkState::Network_Open;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(state == NetWorkState::Network_Connect) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> state = NetWorkState::Network_Close;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当状态增加或发生变化时,我们要修改每一个operator</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">NetworkState</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> NetworkState* pNext;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">Operator2</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">NetworkState</span>() = <span class="keyword">default</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_">OpenState</span> : <span class="keyword">public</span> NetworkState {</span><br><span class="line"> <span class="type">static</span> NetworkState* m_instance;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> NetworkState* <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> OpenState{};</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> m_instance;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> pNext = CloseState::<span class="built_in">getInstance</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator2</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> pNext = ConnectState::<span class="built_in">getInstance</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CloseState</span> : <span class="keyword">public</span> NetworkState {</span><br><span class="line"> <span class="type">static</span> NetworkState* m_instance;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> NetworkState* <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> CloseState{};</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> m_instance;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> pNext = CloseState::<span class="built_in">getInstance</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator2</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> pNext = OpenState::<span class="built_in">getInstance</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectState</span>: <span class="keyword">public</span> NetworkState {</span><br><span class="line"> <span class="type">static</span> NetworkState* m_instance;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> NetworkState* <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(m_instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> m_instance = <span class="keyword">new</span> ConnectState{};</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> m_instance;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> <span class="keyword">override</span> </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">Operator2</span><span class="params">()</span> <span class="keyword">override</span> </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="keyword">class</span> <span class="title class_">NetworkProcessor</span> {</span><br><span class="line"> NetworkState* pState;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">NetworkProcessor</span>(NetworkState* pState) {</span><br><span class="line"> <span class="keyword">this</span>->pState = pState;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator1</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> pState-><span class="built_in">Operator1</span>();</span><br><span class="line"> pState = pState->pNext;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Operator2</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> pState-><span class="built_in">Operator2</span>();</span><br><span class="line"> pState = pState->pNext;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当增加wait状态时,我们只需要写新的waitState即可。</p><blockquote><p>define</p></blockquote><p>允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为</p><p><img src="/img/Snipaste_2025-01-12_12-22-33.png"></p><ul><li><p>State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象切换时,切换相应的对象,但同时维持State的接口,这样实现来具体操作与状态转换之间的解耦</p></li><li><p>为不同状态引入不同的对象使得状态转换变的更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的,即要么彻底转换过来,要么不转换</p></li><li><p>如何State对象没有实例变量,那么各个上下文可以共享一个State对象,从而节省对象开销</p></li></ul><h3 id="备忘录模式"><a href="#备忘录模式" class="headerlink" title="备忘录模式"></a>备忘录模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需求,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Memento</span> {</span><br><span class="line"> std::string state;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Memento</span>(<span class="type">const</span> std::string& s) : <span class="built_in">state</span>(s) { }</span><br><span class="line"> <span class="function">std::string <span class="title">getState</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> state; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setState</span><span class="params">(<span class="type">const</span> std::string& s)</span> </span>{ state = s; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Originator</span> {</span><br><span class="line"> std::string state;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Originator</span>() = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function">Memento <span class="title">createMomento</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">Memento <span class="title">m</span><span class="params">(state)</span></span>;</span><br><span class="line"> <span class="keyword">return</span> m;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setMomento</span><span class="params">(<span class="type">const</span> Memento& m)</span> </span>{ state = m.<span class="built_in">getState</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"> Originator originator;</span><br><span class="line"> Memento mem = originator.<span class="built_in">createMomento</span>(); <span class="comment">// 存储到备忘录</span></span><br><span class="line"> <span class="comment">// ... originator 状态发生改变</span></span><br><span class="line"> originator.<span class="built_in">setMomento</span>(mem); <span class="comment">// 从备忘录中恢复</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>在不破坏封装性的前提下,捕获一个对象内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。</p><p><img src="/img/Snipaste_2025-01-15_12-03-40.png"></p><ul><li><p>备忘录存储原发器(Originator)对象内部状态,在需要时恢复原发器状态</p></li><li><p>Memento模式的核心是信息隐藏,即Originatro需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memento)</p></li><li><p>由于现代语言运行时都具有相当的对象序列化支持,因此往往采用效率高又容易正确实现的序列化方案来实现Memento模式</p></li></ul><h2 id="数据结构模式"><a href="#数据结构模式" class="headerlink" title="数据结构模式"></a>数据结构模式</h2><blockquote><p>常常有一些组件在内部具有特定的数据结构,如果让客户程序依赖这些特定的数据结构,将极大的破坏组件的复用。这时候,将这些特定数据结构封装在内部,在外部提供统一的接口,来实现与特定数据结构无关的访问,是一种行之有效的解决方案</p><p>典型模式:</p><ul><li><p>Composite</p></li><li><p>Iterator</p></li><li><p>Chain of Resposibility</p></li></ul></blockquote><h3 id="组件模式"><a href="#组件模式" class="headerlink" title="组件模式"></a>组件模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件在某些情况下,客户代码过多的依赖与对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性,拓展型等弊端。如何将客户代码与复杂的对象容器结构解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 树节点</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Component</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">process</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Component</span>() = <span class="keyword">default</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_">Composite</span> : <span class="keyword">public</span> Component {</span><br><span class="line"> std::string name;</span><br><span class="line"> std::list<Component*> elements;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Composite</span>(<span class="type">const</span> std::string& s) : <span class="built_in">name</span>(s) { }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(Component* element)</span> </span>{</span><br><span class="line"> elements.<span class="built_in">push_back</span>(element);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">remove</span><span class="params">(Component* element)</span> </span>{</span><br><span class="line"> elements.<span class="built_in">remove</span>(element);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// 1. process cur nodes</span></span><br><span class="line"> <span class="comment">// 2. process leaf nodes</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">auto</span>& e : elements) {</span><br><span class="line"> e-><span class="built_in">process</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">// 叶子节点</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Leaf</span> : <span class="keyword">public</span> Component {</span><br><span class="line"> std::string name;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Leaf</span>(std::string s) : <span class="built_in">name</span>(s) { }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">process</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="comment">// process current node</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">Composite <span class="title">root</span><span class="params">(<span class="string">"root"</span>)</span></span>;</span><br><span class="line"> <span class="function">Composite <span class="title">treeNode1</span><span class="params">(<span class="string">"treeNode1"</span>)</span></span>;</span><br><span class="line"> <span class="function">Composite <span class="title">treeNode2</span><span class="params">(<span class="string">"treeNode2"</span>)</span></span>;</span><br><span class="line"> <span class="function">Composite <span class="title">treeNode3</span><span class="params">(<span class="string">"treeNode3"</span>)</span></span>;</span><br><span class="line"> <span class="function">Composite <span class="title">treeNode4</span><span class="params">(<span class="string">"treeNode4"</span>)</span></span>;</span><br><span class="line"> <span class="function">Leaf <span class="title">left1</span><span class="params">(<span class="string">"left1"</span>)</span></span>;</span><br><span class="line"> <span class="function">Leaf <span class="title">left2</span><span class="params">(<span class="string">"left2"</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"> root.<span class="built_in">add</span>(&treeNode1);</span><br><span class="line"> treeNode1.<span class="built_in">add</span>(&treeNode2);</span><br><span class="line"> treeNode2.<span class="built_in">add</span>(&left1);</span><br><span class="line"> root.<span class="built_in">add</span>(&treeNode3);</span><br><span class="line"> treeNode3.<span class="built_in">add</span>(&treeNode4);</span><br><span class="line"> treeNode4.<span class="built_in">add</span>(&left2);</span><br><span class="line"></span><br><span class="line"> root.<span class="built_in">process</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>将对象组合成树形结构以表示”部分 - 整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)</p><p><img src="/img/Snipaste_2025-01-16_12-10-29.png"></p><ul><li><p>Composite 模式采用树形结构来实现普遍存在的对象容器,从而将一对多的关系转化为一对一的关系,使得客户代码可以一致的处理对象和对象容器,无需关系处理的是单个对象,还是组合对象容器</p></li><li><p>将客户代码与复杂的对象容器结构解耦是Composite的核心思想,解耦后,客户代码将与纯粹的抽象解决(而非对象容器的内部实现结构)发生依赖,从而更能应对变化</p></li><li><p>Composite模式在具体实现中,可以让父对象中的子对象反向追溯,如果父对象有频繁的便利需求,可使用缓存技巧来改善效率</p></li></ul><h3 id="职责链"><a href="#职责链" class="headerlink" title="职责链"></a>职责链</h3><blockquote><p>在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时智能有一个接受者,如果显示指定,将必不可少的带来请求者与接受者的紧耦合。如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum class</span> <span class="title class_">RequeseType</span> {</span><br><span class="line"> REQ_HENDLER1,</span><br><span class="line"> REQ_HENDLER2,</span><br><span class="line"> REQ_HENDLER3</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Request</span> {</span><br><span class="line"> std::string description;</span><br><span class="line"> RequeseType reqType;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Request</span>(<span class="type">const</span> std::string& desc , RequeseType type) </span><br><span class="line"> : <span class="built_in">description</span>(desc) , <span class="built_in">reqType</span>(type) { }</span><br><span class="line"> <span class="function">RequeseType <span class="title">getReqType</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> reqType; }</span><br><span class="line"> <span class="function"><span class="type">const</span> std::string& <span class="title">getDescription</span><span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> description; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ChainHandler</span> {</span><br><span class="line"> ChainHandler* nextChain;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">sendReqestToNextHandler</span><span class="params">(<span class="type">const</span> Request& req)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(nextChain != <span class="literal">nullptr</span>) {</span><br><span class="line"> nextChain-><span class="built_in">handle</span>(req);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">canHandleRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">processRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ChainHandler</span>() { nextChain = <span class="literal">nullptr</span>; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">setNextChain</span><span class="params">(ChainHandler* next)</span> </span>{ nextChain = next; }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">handle</span><span class="params">(<span class="type">const</span> Request& req)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(<span class="built_in">canHandleRequest</span>(req)) {</span><br><span class="line"> <span class="built_in">processRequest</span>(req);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">sendReqestToNextHandler</span>(req);</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="keyword">class</span> <span class="title class_">Headler1</span> : <span class="keyword">public</span> ChainHandler {</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">canHandleRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> req.<span class="built_in">getReqType</span>() == RequeseType::REQ_HENDLER1;</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">processRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Headler1 is handle reqest"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Headler2</span> : <span class="keyword">public</span> ChainHandler {</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">canHandleRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> req.<span class="built_in">getReqType</span>() == RequeseType::REQ_HENDLER2;</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">processRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Headler2 is handle reqest"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Headler3</span> : <span class="keyword">public</span> ChainHandler {</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">canHandleRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> req.<span class="built_in">getReqType</span>() == RequeseType::REQ_HENDLER3;</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">processRequest</span><span class="params">(<span class="type">const</span> Request& req)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Headler3 is handle reqest"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> Headler1 h1;</span><br><span class="line"> Headler2 h2;</span><br><span class="line"> Headler3 h3;</span><br><span class="line"> h1.<span class="built_in">setNextChain</span>(&h2);</span><br><span class="line"> h2.<span class="built_in">setNextChain</span>(&h3);</span><br><span class="line"> <span class="function">Request <span class="title">req</span><span class="params">(<span class="string">"process task"</span>, RequeseType::REQ_HENDLER3)</span></span>;</span><br><span class="line"> h1.<span class="built_in">handle</span>(req);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链请求,直到有一个对象处理它为止。</p><p><img src="/img/Snipaste_2025-01-17_14-18-44.png"></p><ul><li><p>Chain of Responsibility 模式的应用场合在于一个请求可能有多个接受者,但是最后真正的接受者只有一个,这时候请求发送者与接受者的耦合有可能出现变化脆弱的症状,责任链的目的就是将二者解耦,从而更好的应对变化</p></li><li><p>应用来 Chain or Responsibility 模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加或修改的处理职责</p></li><li><p>如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求对象的责任</p></li></ul><h2 id="行为变化模式"><a href="#行为变化模式" class="headerlink" title="行为变化模式"></a>行为变化模式</h2><blockquote><p>在组件的构建过程中,组件行为的变化经常导致组件本身剧烈的变化。行为变化模式将组件的行为和组件本身进行解耦,从而支持行为的变化,实现两者之间的松耦合</p><p>典型模式:</p><ul><li><p>Command</p></li><li><p>Visitor</p></li></ul></blockquote><h3 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h3><blockquote><p>Motivation</p></blockquote><p>在软件构建过程中,行为请求者与行为实现者通常呈现一种紧耦合。但在某些场合,比如需要对行为进行记录 撤销 等处理,这种无法抵御变化的紧耦合是不合适的。这种情况下,如何将行为请求者与行为实现者解耦?这一组行为抽象为对象,可以实现二者的松耦合</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Command</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">execute</span><span class="params">()</span> </span>= <span class="number">0</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_">ConcreteCommand1</span> : <span class="keyword">public</span> Command {</span><br><span class="line"> std::string arg;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConcreteCommand1</span>(<span class="type">const</span> std::string& a) : <span class="built_in">arg</span>(a) { }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">execute</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"#1 process..."</span> << arg << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteCommand2</span> : <span class="keyword">public</span> Command {</span><br><span class="line"> std::string arg;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConcreteCommand2</span>(<span class="type">const</span> std::string& a) : <span class="built_in">arg</span>(a) { }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">execute</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"#2 process..."</span> << arg << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MacroCommand</span> : <span class="keyword">public</span> Command {</span><br><span class="line"> std::vector<Command*> commands;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">addCommand</span><span class="params">(Command* c)</span> </span>{</span><br><span class="line"> commands.<span class="built_in">push_back</span>(c);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">execute</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">auto</span> <span class="type">const</span>& c : commands) {</span><br><span class="line"> c-><span class="built_in">execute</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>将一个请求(行为)封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作</p><ul><li><p>Command 模式的根本目的在于将行为请求者与行为实现者解耦,在面向对象语言中,常见的手段是将行为抽象为对象</p></li><li><p>实现Command接口的具体命令ConcreteCommand有时候更具需要可能会保存一些额外的状态信息。通过使用Composite模式,可以将多个命令封装为一个复合命令 MacroCommadn</p></li><li><p>Command模式与c++中函数对象有些类似。但二者定义行为接口的规范有所区别。Command以面向对象中接口-实现来定义行为接口规范,更严格,但有失性能,c++函数对象以函数签名来定义行为接口规范,更灵活,性能更高</p></li></ul><h3 id="访问器"><a href="#访问器" class="headerlink" title="访问器"></a>访问器</h3><blockquote><p>Motivation</p></blockquote><p>在软件构建过程中,由于需求的改变,某些层次结构中常常需要增加新的行为,如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有的设计。如何在不更改类层次结构的前提下,在<strong>运行时</strong>根据需要透明的为类层次结构上各个类动态的添加新的操作,从而避免上述问题</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ElementA</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ElementB</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Visitor</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">visitElementA</span><span class="params">(ElementA& element)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">visitElementB</span><span class="params">(ElementB& element)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Visitor</span>() = <span class="keyword">default</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_">Element</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">//预知未来会添加新操作,但不知道添加的具体方法</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">accept</span><span class="params">(Visitor& visitor)</span> </span>= <span class="number">0</span>; </span><br><span class="line"> <span class="keyword">virtual</span> ~<span class="built_in">Element</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_">ElementA</span> : <span class="keyword">public</span> Element {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">accept</span><span class="params">(Visitor& visitor)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> visitor.<span class="built_in">visitElementA</span>(*<span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ElementB</span> : <span class="keyword">public</span> Element {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">accept</span><span class="params">(Visitor& visitor)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> visitor.<span class="built_in">visitElementB</span>(*<span class="keyword">this</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">// ==============</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Visitor1</span> : <span class="keyword">public</span> Visitor {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">visitElementA</span><span class="params">(ElementA& element)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Visitor1 is processing ElementA"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">visitElementB</span><span class="params">(ElementB& element)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Visitor1 is processing ElementB"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Visitor2</span> : <span class="keyword">public</span> Visitor {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">visitElementA</span><span class="params">(ElementA& element)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Visitor2 is processing ElementA"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">visitElementB</span><span class="params">(ElementB& element)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Visitor2 is processing ElementB"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> Visitor2 visitor;</span><br><span class="line"> ElementB element;</span><br><span class="line"> element.<span class="built_in">accept</span>(visitor);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>define</p></blockquote><p>表示一个作用于某对象结构中各个元素的操作。使得可以在不改变(稳定)各元素类的前提下定义(拓展)作用于这些元素的新操作(变化)</p><p><img src="/img/Snipaste_2025-01-23_11-04-19.png"></p><ul><li><p>Visitor模式通过所谓的双重分发来实现在不更改Element类层次结构的前提下,在运行时透明的为类层次结构上的各个类动态添加新的操作</p></li><li><p>所谓的双重分发即Visitor模式中间包括两个多态分发,第一个accept方法的多态辨析,第二个为visitElementX方法的多态辨析</p></li><li><p>Visitor模式的最大缺点在于拓展类层次结构(添加新的Element子类),会导致Visitor类的改变。<strong>因此Visitor模式适用于Element类层次结构稳定,而其中的操作却经常面临频繁改动</strong></p></li></ul><p><a href="https://zhuanlan.zhihu.com/p/269835115">https://zhuanlan.zhihu.com/p/269835115</a> </p><p><a href="https://juejin.cn/post/7079753503920357407">C++ 设计模式 - 代理模式在某些情况下,客户端代码不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象 - 掘金</a>, </p>]]></content>
<tags>
<tag> 设计模式 </tag>
</tags>
</entry>
<entry>
<title>秋招杂谈</title>
<link href="/2024/12/12/%E7%A7%8B%E6%8B%9B%E6%97%A5%E8%AE%B0/"/>
<url>/2024/12/12/%E7%A7%8B%E6%8B%9B%E6%97%A5%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="秋招杂谈"><a href="#秋招杂谈" class="headerlink" title="秋招杂谈"></a>秋招杂谈</h1><p>依稀记得当时从足下科技离职,心情倍感焦虑,当时才7月份。从那时就算开启了秋招的进行键,耗时半个月在boss直聘上求职,遇到了云杉网络。</p><p>云杉网络在我实习求职的阶段(4月)就是我意向很强的公司,因为当时我迷上了 dpdk , 而使用 dpdk 开发的岗位较少,所以云杉就成了我在boss直聘上为数不多的关注对象,值得一提的是,后来祛媚了。 原因有以下几点</p><ol><li>挂羊头卖狗肉,招聘软件上写着校招,实际却是实习,有种 hr 冲业绩,能骗一个是一个的感觉</li><li>公司不重视三方协议,在明知道未必有hc的情况,还说可以签三方,还说三方没什么意义,只是给学校看的,和她交流完之后,我后背发凉只想着快点逃离</li><li>公司经营不擅,从我入职以来就没有发过工资,顺便大裁员,但公司承诺n+1, 至少这一点没有突破预期</li><li>裁应届,在放出裁员通知后,一个下午,公司内就没有24届毕业生了。还记得一个测试同学上班三个月,没领到工资还被裁了</li></ol><p>还算幸运,我也收到了两次解除实习协议的暗示,如果这些问题没有暴露出来,不出意外我会好好干活毕业入职,不敢想象… </p><p>即使如此,在我看来这家公司做的事情还是很厉害的,deepflow 也是可观测性领域开源的Top级别了,也许会有很好的前途(至少当时领导对此坚信不疑),但目前一定不是。组内有Rust大佬,ebpf专家,我在的短短一个月也接触到了很多,以至我也成为了Rust爱好者,很是感谢。</p><p>在国庆结束后我便离职回学校继续秋招,10月已经是秋招的尾声了,基本没什么笔试约面也很少,一度以为自己完蛋了,双非本的bg,那段时间活在焦虑之中,还记得屏幕上的提示”今日沟通人数已达上限,请明天再试”,默默关掉手机,看着屏幕上的小丑</p><p>高强度的找工作效果不错,每周都有面试,11月后面试频率更高了。最终选择了小米,这也是唯二面过的大厂(还有字节 二面挂),我运气很好,也很感谢小米。</p>]]></content>
<tags>
<tag> life </tag>
</tags>
</entry>
<entry>
<title>决胜网络协议</title>
<link href="/2024/07/20/%E5%86%B3%E8%83%9C%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
<url>/2024/07/20/%E5%86%B3%E8%83%9C%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/</url>
<content type="html"><![CDATA[<h3 id="序列号回绕"><a href="#序列号回绕" class="headerlink" title="序列号回绕"></a>序列号回绕</h3><p>我们这里假设只有 8位 用来描述<code> tcp</code> 的序列号</p><p>seq1 = 255 seq2 = 1</p><p>使用减法来判断包的先后顺序 ,</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">bool</span> <span class="title function_">before</span><span class="params">(__u32 seq1, __u32 seq2)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> (__s32)(seq1-seq2) < <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> after(seq2, seq1) before(seq1, seq2)</span></span><br></pre></td></tr></table></figure><p>这里就变为了 before(0b1111 , 0b1) -> return 0b1110 < 0 , 采用了无符号数减法转化为有符号数来粗略的判断,当seq1 和 seq2之间的间距 大于 0xc000 时就无法判断了,就需要使用额外的东西来判断如时间戳</p><h3 id="窗口缩放"><a href="#窗口缩放" class="headerlink" title="窗口缩放"></a>窗口缩放</h3><p>tcp头部中指定了窗口大小,使用了16位,这意味着最大窗口位 64k,由于现代网络传输速度的加快,64k有时不能满足要求,于是在这上面打了补丁也就有了窗口缩放,用窗口大小乘缩放因子得到时机的窗口大小,缩放因子是在三次握手中协商的,如下报文</p><p><img src="/img/Snipaste_2024-07-10_20-12-53.png"></p><p>这里的缩放因子是 256 也就是实际的窗口大小为 516 * 256</p><h4 id="tcp的重要选项"><a href="#tcp的重要选项" class="headerlink" title="tcp的重要选项"></a>tcp的重要选项</h4><p> 在 <code>tcp</code>头部中选项和填充属于可选字段,但也有相当重要的</p><p>MSS: 最大段大小选项 , TCP 允许的从对方接收的最大报文段<br>SACK: 选择确定选项<br>Window Scale: 窗口缩放选项</p><h3 id="临时端口的分配"><a href="#临时端口的分配" class="headerlink" title="临时端口的分配"></a>临时端口的分配</h3><p> 在没有调用 bind 或者 bind 指定的端口号为 0 的时候会采用临时端口号</p><p>tcp协议栈用三个全局的 <code>inet_hash</code> 哈希表</p><ul><li>ehash: 负责有名的 socket,也就是四元组明确的 socket , key 是源地址 源端口 目的地址 目的端口组成的 , value 是对应的socket</li><li>bhash: 负责端口分配,key是端口号,value 是使用此端口的所有socket,一个socket 可同时在bhash 和 ehash 中使用</li><li>listening_hash: 负责 listen socket</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_hashinfo</span> {</span></span><br><span class="line"><span class="comment">/* This is for sockets with full identity only. Sockets here will</span></span><br><span class="line"><span class="comment"> * always be without wildcards and will have the following invariant:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_ehash_bucket</span>*<span class="title">ehash</span>;</span></span><br><span class="line"><span class="type">spinlock_t</span>*ehash_locks;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>ehash_mask;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>ehash_locks_mask;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Ok, let's try this, I give up, we do need a local binding</span></span><br><span class="line"><span class="comment"> * TCP hash as well as the others for fast bind/connect.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kmem_cache</span>*<span class="title">bind_bucket_cachep</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_bind_hashbucket</span>*<span class="title">bhash</span>;</span></span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>bhash_size;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* The 2nd listener table hashed by local port and address */</span></span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>lhash2_mask;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_listen_hashbucket</span>*<span class="title">lhash2</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>inet_ehash_bucket</code> 说是哈希表,实际就是一个数组,数组的每一个元素都是一个 <code>inet_bind_hashbuchet</code> 指针,chain 字段是一个链表</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_ehash_bucket</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_nulls_head</span> <span class="title">chain</span>;</span></span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_bind_hashbucket</span> {</span></span><br><span class="line"><span class="type">spinlock_t</span>lock;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_head</span><span class="title">chain</span>;</span></span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_listen_hashbucket</span> {</span></span><br><span class="line"><span class="type">spinlock_t</span>lock;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_nulls_head</span><span class="title">nulls_head</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>bind(0) 分配端口:</p><p><img src="/img/Snipaste_2024-07-11_11-50-02.png"></p><p><img src="/img/Snipaste_2024-07-11_14-06-37.png"></p><p>connect 分配端口:</p><p>首先要分配端口,与 bind(0) 不同的是 <code>offset &= ~1</code>,将offset 强制变为偶数之后与low相加检测bhash中是否有相同的 ,之后判断端口是否可用</p><p>对应 bind 端口为 0 时 ,所分配的临时端口是奇数,对应没有 bind 直接 connect ,所分配的端口是偶数</p><blockquote><p>高版本内核临时端口分配策略</p><p>/proc/sys/net/ipv4/ip_local_port_range 文件指定了临时端口号的下界 low 和 上界 high,默认情况下 low 是偶数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@bool:~# cat /proc/sys/net/ipv4/ip_local_port_range </span><br><span class="line">3276860999</span><br></pre></td></tr></table></figure><ul><li>优先给 bind(0) 分配随机的与low奇偶性不同的端口,也就是奇数,如果奇数端口号分配完了才需要尝试分配偶数端口</li><li>优先给 connect 分配随机的与low奇偶性相同的端口,也就是偶数</li></ul></blockquote><p>协议栈端口选择导致的性能衰减:当客户端疯狂 connect 时,1w并发连接和2w并发连接的耗时完全无线性关系,而是呈现一种近指数上升的趋势。例如,1w并发链接建连1w次,耗时不到1s,如果改为2w并发链接,建连2w次,耗时突然变成了10+s。当我们完全占用偶数组的端口后,所有后续的<code>connect</code>调用,所需的源端口应该位于奇数组中,然而该函数依旧会尝试完整遍历偶数组资源,这也就是<code>__inet_check_established</code>耗时占比这么高的原因。简而言之,就是自kernel 4.2开始,端口资源的分配策略改了,目前奇数端口留给<code>bind</code>,偶数端口留给<code>connect</code>为了均衡资源的占用,但是显然,这种策略不适合本文所述的特殊场景,并且对于<code>bind</code>而言,也存在性能衰减的问题。</p><p>为什么 SYN / FIN段不携带数据却要消耗一个序列号?</p><ul><li>不占用序列号的段是不需要确认的,比如纯 ACK 包</li><li>SYN 段需要双方确认,需要占用一个序列号,若不进行确认,对端将无法辨别所发出的 SYN 是否已经被收到</li><li>凡是消耗序列号的 TCP 报文段,一定需要对端确认。如果这个段没有收到确认,会一直重传直到达到指定的次数为至</li></ul><h3 id="三次握手冷知识"><a href="#三次握手冷知识" class="headerlink" title="三次握手冷知识"></a>三次握手冷知识</h3><p>通信双方都知道对端开发的端口,同时调用 connect , 发送 syn 包,双方进入 SYN_SEND 状态,接收到对端传来的 SYN 时进入 SYN_REVD , 发送 SYN ACK , 进入 ESTABLISH</p><p><img src="/img/Snipaste_2024-07-12_14-06-24.png"></p><p>TCP 自连接:</p><p>当我们执行这段脚本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">while ture</span><br><span class="line">do </span><br><span class="line">telnet 127.0.0.1 50000</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>过一段时间会出现</p><p><img src="/img/Snipaste_2024-07-12_14-19-31.png"></p><p>原理:</p><p><img src="/img/Snipaste_2024-07-12_14-23-25.png"></p><p>自连接的危害:</p><p>存在业务系统 B会访问本机服务 A , 服务A监听了 50000 端口</p><ol><li>业务系统B代码中增加了对服务A断开重连的逻辑</li><li>如果有一个服务A挂掉长时间没有启动,业务系统B开始不断的 connect</li><li>系统B经过一段时间的重试就会出现自连接的情况</li><li>这是服务A想要监听50000断开就会出现断开被占用的情况</li></ol><p>自连接的进程占用了端口,导致真正需要监听端口的服务程序无法正常启动,自连接的进程开启了connect成功了,实际上服务是不正常的,无法正常进行数据通信</p><h3 id="四次挥手中的同时关闭"><a href="#四次挥手中的同时关闭" class="headerlink" title="四次挥手中的同时关闭"></a>四次挥手中的同时关闭</h3><p><img src="/img/Snipaste_2024-07-13_00-01-43.png"></p><h3 id="两个队列"><a href="#两个队列" class="headerlink" title="两个队列"></a>两个队列</h3><p>半连接队列(Incomplete connection queue), 又称 SYN 队列<br>全连接队列(Completed connection queue), 又称 Accept 队列</p><p>当客户端发起 SYN 到服务端,服务端收到以后会回 ACK 和⾃⼰的 SYN。这时服务端这边的 TCP 从 listen 状态变为 SYN_RCVD (SYN Received),此时会将这个连接信息放⼊半连接队列</p><p><code>int listen(int fd , int backlog)</code></p><p>TCP套接字上的backlog参数的行为在Linux 2.2中发生了变化。现在,它指定等待接受的完全建立的套接字的队列长度,而不是不完整连接请求的数量。不完整套接字队列的最大长度可以使用 <code>/proc/sys/net/ipv4/tcp_max_syn_backlog</code> 设置。当启用同步cookie时,没有逻辑上的最大长度,此设置将被忽略。</p><p>如果backlog参数大于 <code>/proc/sys/net/core/somaxconn</code> 中的值,那么它将被静默地截断为该值。自Linux 5.4以来,此文件中的默认值为4096</p><p>半连接队列的大小和传入的 backlog , 系统参数 <code>somaxconn</code> ,<code>max_syn_backlog</code> 都有关系,通过其中最小值用于计算,盲目的调大backlog 对最终半连接队列的大小可能不会有影响</p><p>全连接队列包含了服务端所有完成了三次握⼿,但是还未被应⽤取⾛的连接队列。此时的 socket 处于 ESTABLISHED 状态。每次应⽤调⽤ accept() 函数会移除队列头的连接。如果队列为空, accept() 通常会阻塞。全连接队列也被称为 Accept 队列。</p><p>半连接队列满了以后会忽略 SYN<br>全连接队列满了之后会忽略客户端的 ACK,随后重传 SYN + ACK,可以通过 <code>/proc/sys/net/ipv4/tcp_abort_on_overflow</code> 决定</p><ul><li><code>tcp_abort_on_overflow</code> 为 0 表示三次握⼿最后⼀步全连接队列满以后 server 会丢掉 client 发过来的 ACK,服务端随后会进⾏重传 SYN+ACK。</li><li><code>tcp_abort_on_overflow</code> 为 1 表示全连接队列满以后服务端直接 发送 RST 给客户端。</li></ul><p>但是回给客户端 RST 包会带来另外⼀个问题,客户端不知道服务端 响应的 RST 包到底是因为「该端⼝没有进程监听」,还是「该端⼝ 有进程监听,只是它的队列满了」。</p><h3 id="TCP的定时器"><a href="#TCP的定时器" class="headerlink" title="TCP的定时器"></a>TCP的定时器</h3><p><code>tcp</code> 为每条连接建立了 7 个定时器:连接建立定时器,重传定时器,延迟ACK定时器,PERSIST定时器,KEEPALIVE定时器,FINWAIT2定时器,TIME_WAIT定时器 </p><p>Persist 定时器是专⻔为零窗⼝探测⽽准备的。我们都知道 TCP 利⽤滑动窗⼝来实现流量控制,当接收端 B 接收窗⼝为 0 时,发送端 A 此时不 能再发送数据,发送端此时开启 Persist 定时器,超时后发送⼀个特殊的报⽂给接收端看对⽅窗⼝是否已经恢复,这个特殊的报⽂只有⼀个字节。</p><p>四次挥⼿过程中,主动关闭的⼀⽅收到 ACK 以后从 FINWAIT1 进⼊ FINWAIT2 状态等待对端的 FIN 包的到来,FINWAIT2 定时器的作 ⽤是防⽌对⽅⼀直不发送 FIN 包,防⽌⾃⼰⼀直傻等。这个值由 <code>/proc/sys/net/ipv4/tcp_fin_timeout</code> 决定,默认值为 60s</p><p>TIMEWAIT 定时器也称为 2MSL 定时器,可主动关闭连接的⼀⽅在 TIMEWAIT 持续 2 个 MSL 的时间,超时后端⼝号可被安全的重⽤。</p><h3 id="TCP-拥塞控制"><a href="#TCP-拥塞控制" class="headerlink" title="TCP 拥塞控制"></a>TCP 拥塞控制</h3><p>tcp 拥塞控制四大阶段:慢启动 , 拥塞避免 , 快速重传 , 快速恢复</p><p>在连接建⽴之初,你不知道对端有多快,如果有⾜够的带宽,你可以选择⽤最快的速度传输数据,但是如果 是⼀个缓慢的移动⽹络呢?如果发送的数据过多,只是造成更⼤的⽹络延迟。这是基于整 个考虑,每个 TCP 连接都有⼀个拥塞窗⼜的限制,最初这个值很⼩,随着时间的推移, 每次发送的数据量如果在不丢包的情况下,慢慢的递增,这种机制被称为「慢启动」</p><blockquote><p> 第⼀步,三次握⼿以后,双⽅通过 ACK 告诉了对⽅ ⾃⼰的接收窗⼜(rwnd)的⼤⼩,之后就可以互相发 数据了</p><p>第⼆步,通信双⽅各⾃初始化⾃⼰的「拥塞窗⼜」 (Congestion Window,cwnd)⼤⼩</p><p>第三步,cwnd 初始值较⼩时,每收到⼀个 ACK, cwnd + 1,每经过⼀个 RTT,cwnd 变为之前的两 倍。</p></blockquote><p><img src="/img/Snipaste_2024-07-20_14-58-22.png"></p><p>慢启动拥塞窗口(cwnd)肯定不能⽆⽌境的指数级增长下去,否则拥塞控制就变成了「拥塞失控」了,它的阈值称为「慢启动阈值」(Slow Start Threshold,ssthresh)。<code>ssthresh</code> 就是⼀道刹车,让拥塞窗⼜别涨那么快</p><ul><li>当 cwnd < ssthresh 时,拥塞窗⼜按指数级增长(慢启动)</li><li>当 cwnd > ssthresh 时,拥塞窗⼜按线性增长(拥塞避免),在这个阶段,每⼀ 个往返 RTT,拥塞窗⼜⼤约增加 1 个 MSS ⼤⼩,直到检测到拥塞为⽌</li></ul><p>当收到三次重复 ACK 时,进⼊快速恢复阶段。解释为⽹络轻度拥塞。</p><ul><li>拥塞阈值 ssthresh 降低为 cwnd 的⼀半:ssthresh = cwnd / 2</li><li>拥塞窗⼜ cwnd 设置为 ssthresh</li><li>拥塞窗⼜线性增加</li></ul>]]></content>
<tags>
<tag> read </tag>
<tag> 网络编程 </tag>
</tags>
</entry>
<entry>
<title>io_uring</title>
<link href="/2024/02/05/io_uring/"/>
<url>/2024/02/05/io_uring/</url>
<content type="html"><![CDATA[<h1 id="io-uring"><a href="#io-uring" class="headerlink" title="io_uring"></a>io_uring</h1><p>在linux 5.1 版本之后,Linux内核提供了异步IO的框架支持,提供了三个系统调用 <code> io_uring_enter io_uring_register io_uring_setup</code> 在liburing.h头文件中对此系统调用进行了封装,如:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_enter</span><span class="params">(<span class="type">int</span> ring_fd, <span class="type">unsigned</span> <span class="type">int</span> to_submit,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> <span class="type">int</span> min_complete, <span class="type">unsigned</span> <span class="type">int</span> flags)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> (<span class="type">int</span>) syscall(__NR_io_uring_enter, ring_fd, to_submit, min_complete,</span><br><span class="line"> flags, <span class="literal">NULL</span>, <span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中系统调用号<code>__NR_io_uring_enter</code> 在<code>unistd_64.h</code>中被定义:<code>#define __NR_io_uring_enter 426</code></p><h2 id="性能:"><a href="#性能:" class="headerlink" title="性能:"></a>性能:</h2><p>由于调用系统调用时,会从用户态切换到内核态,从而进行上下文切换,而上下文切换会消耗一定的 CPU 时间。<code>io_uring</code> 为了减少或者摒弃系统调用,采用了用户态与内核态共享内存的方式来通信(用户态对共享内存进行读写操作是不需要使用系统调用的,所以不会发生上下文切换的情况)。</p><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_setup</span><span class="params">(u32 entries, <span class="keyword">struct</span> io_uring_params *p)</span>;</span><br></pre></td></tr></table></figure><p>用户通过调用 <code>io_uring_setup </code>初始化一个新的 <code>io_uring</code> 上下文。该函数返回一个 file descriptor,并将 <code>io_uring</code> 支持的功能、以及各个数据结构在 <code>fd</code> 中的偏移量存入 <code>params</code>。用户根据偏移量将 <code>fd</code> 映射到内存 (mmap) 后即可获得一块内核用户共享的内存区域。这块内存区域中,有 <code>io_uring</code> 的上下文信息:提交队列信息 (<code>SQ_RING</code>) 和完成队列信息 (<code>CQ_RING</code>);还有一块专门用来存放提交队列元素的区域 (SQEs)。<code>SQ_RING</code> 中只存储 SQE 在 SQEs 区域中的序号,<code>CQ_RING</code> 存储完整的任务完成数据。</p><p><img src="/img/v2-ad01522fd88442e9164001926b3d839c_r.png"></p><p><code>io_uring</code> 在创建时有两个选项(flag),对应着 <code>io_uring</code> 处理任务的不同方式:</p><ul><li>开启 <code>IORING_SETUP_IOPOLL</code> 后,<code>io_uring</code> 会使用轮询的方式执行所有的操作。</li><li>开启 <code>IORING_SETUP_SQPOLL</code> 后,<code>io_uring</code> 会创建一个内核线程专门用来收割用户提交的任务。</li><li>都不开启,通过 <code>io_uring_enter</code> 提交任务,收割任务无需 syscall。</li></ul><p><code>io_uring_setup</code> 设计的巧妙之处在于,内核通过一块和用户共享的内存区域进行消息的传递。在创建上下文后,任务提交、任务收割等操作都通过这块共享的内存区域进行,在 <code>IO_SQPOLL</code> 模式下,可以完全绕过 Linux 的 syscall 机制完成需要内核介入的操作(比如读写文件),大大减少了 syscall 切换上下文、刷 TLB 的开销。</p><h2 id="任务的定义"><a href="#任务的定义" class="headerlink" title="任务的定义"></a>任务的定义</h2><p><code>io_uring</code> 定义的异步 io 请求,对应宏定义</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_NOP0</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_READV1</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_WRITEV2</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_FSYNC3</span></span><br></pre></td></tr></table></figure><p>内核中定义了<code>io_op_defs</code>数组用与描述对应的异步 io 请求所需要的条件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">io_op_def</span> <span class="title">io_op_defs</span>[] =</span> {</span><br><span class="line">[IORING_OP_NOP] = {},</span><br><span class="line">[IORING_OP_READV] = {</span><br><span class="line">.needs_file= <span class="number">1</span>,<span class="comment">//表示该操作需要文件描述符。</span></span><br><span class="line">.unbound_nonreg_file= <span class="number">1</span>, <span class="comment">//表示该操作需要非正则文件</span></span><br><span class="line">.pollin= <span class="number">1</span>,<span class="comment">//表示该操作需要poll in。</span></span><br><span class="line">.buffer_select= <span class="number">1</span>,<span class="comment">//表示该操作需要buffer_select</span></span><br><span class="line">.needs_async_setup= <span class="number">1</span>,<span class="comment">//表示该操作需要异步设置。</span></span><br><span class="line">.plug= <span class="number">1</span>, <span class="comment">//表示该操作需要plug。 </span></span><br><span class="line">.async_size= <span class="keyword">sizeof</span>(<span class="keyword">struct</span> io_async_rw),<span class="comment">//表示该操作的异步结构体大小</span></span><br><span class="line">},</span><br><span class="line">[IORING_OP_WRITEV] = {</span><br><span class="line">.needs_file= <span class="number">1</span>,</span><br><span class="line">.hash_reg_file= <span class="number">1</span>,</span><br><span class="line">.unbound_nonreg_file= <span class="number">1</span>,</span><br><span class="line">.pollout= <span class="number">1</span>,</span><br><span class="line">.needs_async_setup= <span class="number">1</span>,</span><br><span class="line">.plug= <span class="number">1</span>,</span><br><span class="line">.async_size= <span class="keyword">sizeof</span>(<span class="keyword">struct</span> io_async_rw),</span><br><span class="line">},</span><br><span class="line"> ....</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>io_uring</code> 中几乎每个操作都有对应的准备和执行函数。比如 <code>read</code> 操作就对应 <code>io_read_prep</code> 和 <code>io_read</code> 函数。除了同步操作,内核还支持异步调用的操作,对于这些操作,<code>io_uring</code>中还会有一个对应的异步准备函数以 <code>_async</code> 结尾</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">io_sendmsg_prep_async</span><span class="params">(<span class="keyword">struct</span> io_kiocb *req)</span></span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">int</span> <span class="title function_">io_rw_prep_async</span><span class="params">(<span class="keyword">struct</span> io_kiocb *req, <span class="type">int</span> rw)</span>;</span><br></pre></td></tr></table></figure><h2 id="任务的创建"><a href="#任务的创建" class="headerlink" title="任务的创建"></a>任务的创建</h2><p><strong>用户将需要进行的操作写入 <code>io_uring</code> 的 SQ 中。在 CQ 中,用户可以收割任务的完成情况。</strong></p><p>sqe的结构:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> {</span></span><br><span class="line">__u8opcode;<span class="comment">/* type of operation for this sqe */</span></span><br><span class="line">__u8flags;<span class="comment">/* IOSQE_ flags */</span></span><br><span class="line">__u16ioprio;<span class="comment">/* ioprio for the request */</span></span><br><span class="line">__s32fd;<span class="comment">/* file descriptor to do IO on */</span></span><br><span class="line">__u64off;<span class="comment">/* offset into file */</span></span><br><span class="line">__u64addr;<span class="comment">/* pointer to buffer or iovecs */</span></span><br><span class="line">__u32len;<span class="comment">/* buffer size or number of iovecs */</span></span><br><span class="line"><span class="class"><span class="keyword">union</span> {</span></span><br><span class="line"><span class="type">__kernel_rwf_t</span>rw_flags;</span><br><span class="line">__u32fsync_flags;</span><br><span class="line">__u16poll_events;</span><br><span class="line">__u32sync_range_flags;</span><br><span class="line">__u32msg_flags;</span><br><span class="line">__u32timeout_flags;</span><br><span class="line">};</span><br><span class="line">__u64user_data;<span class="comment">/* data to be passed back at completion time */</span></span><br><span class="line"><span class="class"><span class="keyword">union</span> {</span></span><br><span class="line">__u16buf_index;<span class="comment">/* index into fixed buffers, if used */</span></span><br><span class="line">__u64__pad2[<span class="number">3</span>];</span><br><span class="line">};</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>如要进行<code>readv</code> 操作:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sqe->fd = filefd;<span class="comment">//需要操作的文件描述符</span></span><br><span class="line">sqe->flags = <span class="number">0</span>;</span><br><span class="line">sqe->opcode = IORING_OP_READV; <span class="comment">//readv对应option_code</span></span><br><span class="line">sqe->addr = &iovecs; <span class="comment">//存放的起始地址</span></span><br><span class="line">sqe->len = blocks;<span class="comment">//对应 iovec 数组的长度</span></span><br><span class="line">sqe->off = <span class="number">0</span>;<span class="comment">//从文件偏移位置为0处开始</span></span><br></pre></td></tr></table></figure><p>通常来说,使用 <code>io_uring</code> 的程序都需要用到 64 位的 <code>user_data</code> 来唯一标识一个操作。<code>user_data</code> 是 SQE 的一部分。<code>io_uring</code> 执行完某个操作后,会将这个操作的 <code>user_data</code> 和操作的返回值一起写入 CQ 中。一般携带指向堆内存的指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> {</span></span><br><span class="line">__u64user_data;<span class="comment">/* sqe->data submission passed back */</span></span><br><span class="line">__s32res;<span class="comment">/* result code for this event */</span></span><br><span class="line">__u32flags;<span class="comment">// 未使用</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p> <code>io_ uring</code> 是一个异步接口,<code>errno</code> 将不用于传回错误信息。与此对应,<code>res</code> 将保存在成功的情况下等效的系统调用将要返回的内容,而在出错的情况下 <code>res</code> 将包含<code>-errno</code>。例如,如果正常读取系统调用返回<code>-1</code>并将<code>errno</code>设置为<code>EINVAL</code>,则<code>res</code>将包含<code>-EINVAL</code>。</p><h2 id="任务的提交与收割"><a href="#任务的提交与收割" class="headerlink" title="任务的提交与收割"></a>任务的提交与收割</h2><p><code>io_uring</code> 通过环形队列和用户交互。</p><p><img src="/img/v2-f688ede3a66c848fb4e3333767dfd9cc_r.png"></p><p>我们的先以用户提交任务为例,介绍 <code>io_uring</code> 的内核用户交互方式。用户提交任务的过程如下:</p><ul><li>将 SQE 写入 SQEs 区域,而后将 SQE 编号写入 SQ。(对应图中绿色第一步)</li><li>更新用户态记录的队头。(对应图中绿色第二步)</li><li>如果有多个任务需要同时提交,用户不断重复上面的过程。</li><li>将最终的队头编号写入与内核共享的 <code>io_uring</code> 上下文。(对应图中绿色第三步)</li></ul><p>接下来我们简要介绍内核获取任务、内核完成任务、用户收割任务的过程。</p><ul><li>内核态获取任务的方式是,从队尾读取 SQE,并更新 <code>io_uring</code> 上下文的 SQ tail。</li></ul><p><img src="/img/v2-670198a5e28380ee33809eec41d39e04_r.png"></p><ul><li>内核态完成任务:往 CQ 中写入 CQE,更新上下文 CQ head。</li><li>用户态收割任务:从 CQ 中读取 CQE,更新上下文 CQ tail。</li></ul><p><code>io_uring</code> 在创建时有两个选项,对应着 <code>io_uring</code> 处理任务的不同方式:</p><ul><li>开启 <code>IORING_SETUP_IOPOLL</code> 后,<code>io_uring</code> 会使用轮询的方式执行所有的操作。</li><li>开启 <code>IORING_SETUP_SQPOLL</code> 后,<code>io_uring</code> 会创建一个内核线程专门用来收割用户提交的任务。</li></ul><p>这些选项的设定会影响之后用户与 <code>io_uring</code> 交互的方式:</p><ul><li>都不开启,通过 <code>io_uring_enter</code> 提交任务,收割任务无需 syscall。</li><li>只开启 <code>IORING_SETUP_IOPOLL</code>,通过 <code>io_uring_enter</code> 提交任务和收割任务。</li><li>开启 <code>IORING_SETUP_SQPOLL</code>,无需任何 syscall 即可提交、收割任务。内核线程在一段时间无操作后会休眠,可以通过 <code>io_uring_enter</code> 唤醒。</li></ul><h2 id="liburing"><a href="#liburing" class="headerlink" title="liburing"></a>liburing</h2><p>使用流程:</p><ol><li><p>使用io_uring_queue_init,完成io_uring相关结构的初始化。在这个函数的实现中,会调用多个mmap来初始化一些内存。</p></li><li><p>初始化完成之后,为了提交IO请求,需要获取里面queue的一个项,使用io_uring_get_sqe。</p></li><li><p>获取到了空闲项之后,使用io_uring_prep_readv、io_uring_prep_writev初始化读、写请求。和前文所提preadv、pwritev的思想差不多,这里直接以不同的操作码委托io_uring_prep_rw,io_uring_prep_rw只是简单地初始化io_uring_sqe。</p></li><li><p>准备完成之后,使用io_uring_submit提交请求。</p></li><li><p>提交了IO请求时,可以通过非阻塞式函数io_uring_peek_cqe、阻塞式函数io_uring_wait_cqe获取请求完成的情况。默认情况下,完成的IO请求还会存在内部的队列中,需要通过io_uring_cqe_seen表标记完成操作。</p></li><li><p>使用完成之后要通过io_uring_queue_exit来完成资源清理的工作。</p></li></ol><h3 id="link-operation"><a href="#link-operation" class="headerlink" title="link operation"></a>link operation</h3><p>在io_uring中完成的任务并不是按照提交顺序返回的,有时我们需要按顺序的完成一组任务,这需要设置<code>sqe</code>对应的flag,为<code>flag</code>添加 <code>IOSQE_IO_LINK</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">io_uring_prep_write(sqe, fd, STR, <span class="built_in">strlen</span>(STR), <span class="number">0</span> );</span><br><span class="line">sqe->flags |= IOSQE_IO_LINK;<span class="comment">//添加link flag</span></span><br><span class="line"></span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line"><span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">io_uring_prep_read(sqe, fd, buff, <span class="built_in">strlen</span>(STR),<span class="number">0</span>);</span><br><span class="line">sqe->flags |= IOSQE_IO_LINK;<span class="comment">//添加link flag</span></span><br><span class="line"></span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line"><span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line">io_uring_prep_close(sqe, fd);</span><br><span class="line">io_uring_submit(ring);</span><br></pre></td></tr></table></figure><p><code>IOSQE_IO_LINK</code>使得本<code>sqe</code>与下一提交的<code>sqe</code>相关联,即两个任务之间有了先后顺序,如上代码就保证了,先读后写最后关闭</p><p>倘若我们操作的文件没有对应的权限,如没有写权限,文件以 O_WRONLY 打开,那么read操作将失败,这将导致后续link的操作全部失败</p><p>当涉及链接操作时,一个操作的失败将导致所有后续链接操作失败,并出现 errno“Operation cancelled”</p><h3 id="regster"><a href="#regster" class="headerlink" title="regster"></a>regster</h3><p>注册文件或用户缓冲区允许内核长期引用内部数据结构或创建应用程序内存的长期映射,从而大大减少每个I/O的开销。</p><p>应用程序可以增加或减少已注册缓冲区的大小或数量,方法是首先取消注册现有缓冲区,然后使用新缓冲区发出对io_uring_register()的新调用。注册缓冲区将等待环空闲。如果应用程序当前有正在处理的请求,注册将等待这些请求完成后再继续。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_buffers</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">const</span> <span class="keyword">struct</span> iovec *iovecs, <span class="type">unsigned</span> nr_iovecs)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">io_uring_prep_write_fixed</span><span class="params">(<span class="keyword">struct</span> io_uring_sqe *sqe,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> fd,</span></span><br><span class="line"><span class="params"> <span class="type">const</span> <span class="type">void</span> *buf,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> nbytes,</span></span><br><span class="line"><span class="params"> __u64 offset,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> buf_index)</span>;</span><br><span class="line"> <span class="type">void</span> <span class="title function_">io_uring_prep_read_fixed</span><span class="params">(<span class="keyword">struct</span> io_uring_sqe *sqe,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> fd,</span></span><br><span class="line"><span class="params"> <span class="type">void</span> *buf,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> nbytes,</span></span><br><span class="line"><span class="params"> __u64 offset,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> buf_index)</span>;</span><br></pre></td></tr></table></figure><p>在register后,对映射后的内存进行read / write 操作时,避免一次数据copy,register可以理解为将iovec mmap 到内核中,这样在进行read 或 write 后就少了一次copy</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> fd = open(FILE_NAME, O_RDWR|O_TRUNC|O_CREAT, <span class="number">0644</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">4</span>; i++) {</span><br><span class="line"> iov[i].iov_base = <span class="built_in">malloc</span>(BUF_SIZE);</span><br><span class="line"> iov[i].iov_len = BUF_SIZE;</span><br><span class="line"> <span class="built_in">memset</span>(iov[i].iov_base, <span class="number">0</span>, BUF_SIZE);</span><br><span class="line">}</span><br><span class="line"><span class="type">int</span> ret = io_uring_register_buffers(ring, iov, <span class="number">4</span>);</span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line">io_uring_prep_write_fixed(sqe, fd, iov[<span class="number">0</span>].iov_base, str1_sz, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">io_uring_submit(ring);</span><br><span class="line">io_uring_wait_cqe(ring, &cqe);</span><br><span class="line">io_uring_cqe_seen(ring, cqe);</span><br></pre></td></tr></table></figure><p>注册文件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_files</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">const</span> <span class="type">int</span> *files , <span class="type">unsigned</span> nr_files)</span>;</span><br></pre></td></tr></table></figure><p>在用于提交的SQE中,您在使用文件描述符数组中的文件描述符索引而不是在像<code> io_uring_prep_readv()</code>和<code>io_uring_prep_writev()</code>这样的调用中使用文件描述符本身时设置了<code>IOSQE_FIXED_FILE</code> 标志。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> <span class="type">int</span> fds[<span class="number">2</span>];</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> *<span class="title">sqe</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> *<span class="title">cqe</span>;</span></span><br><span class="line"> <span class="type">int</span> ret = io_uring_register_files(ring, fds, <span class="number">1</span>);</span><br><span class="line"> sqe = io_uring_get_sqe(ring);</span><br><span class="line"> io_uring_prep_write(sqe, <span class="number">0</span>, buff1, str1_sz, <span class="number">0</span>);</span><br><span class="line"> <span class="comment">//这里的0表示注册的一组文件描述符的索引只有在设置flag |= IOSQE_FIXED_FILE 有效</span></span><br><span class="line"> sqe->flags |= IOSQE_FIXED_FILE;</span><br><span class="line">io_uring_submit(ring);</span><br></pre></td></tr></table></figure><h3 id="queue-polling"><a href="#queue-polling" class="headerlink" title="queue polling"></a>queue polling</h3><p>减少系统调用的数量是IO的一个主要目标。为此,<code>io_uring</code>允许提交I/O请求,而无需进行单个系统调用。这是通过<code>io_uring</code>支持的一个特殊的提交队列轮询特性完成的。在这种模式下,在程序设置轮询模式后,<code>o_uring</code>启动一个特殊的内核线程,该线程轮询共享提交队列中程序可能添加的条目。这样,您只需将条目提交到共享队列中,内核线程应该看到它并拾取提交队列条目,而无需您的程序进行io_uring_enter()系统调用。这是在用户空间和内核之间共享队列的一个好处。</p><p>通过在io_uring_params结构的flags成员中设置IORING_SETUP_SQPOLL标志,可以告诉io_uring要使用此模式。如果内核线程在一段时间内没有看到任何提交,它将退出,需要再次调用io_uring_enter()系统调用来唤醒内核线程,这里的时间由 io_uring_param 的成员 <code>sq_thread_idle</code> 所决定</p><p>在使用liburing时,您永远不会直接调用 <code> io_uring_enter()</code>系统调用。这通常是由<code>liburing</code>的<code> io_uring_submit()</code>函数来处理的。它会自动判断你是否在使用轮询模式,并处理你的程序何时需要调用<code> io_uring_enter()</code>,而你不必为此费心。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring</span> <span class="title">ring</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_params</span> <span class="title">params</span>;</span></span><br><span class="line"><span class="built_in">memset</span>(&params, <span class="number">0</span>, <span class="keyword">sizeof</span>(params));</span><br><span class="line">params.flags |= IORING_SETUP_SQPOLL;<span class="comment">// 设置poll模式</span></span><br><span class="line">params.sq_thread_idle = <span class="number">2000</span>;</span><br><span class="line">io_uring_queue_init_params(<span class="number">8</span>, &ring, &params);</span><br></pre></td></tr></table></figure><h3 id="eventfd"><a href="#eventfd" class="headerlink" title="eventfd"></a>eventfd</h3><p>首先我们回顾下 eventfd 系统调用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">eventfd</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> initval, <span class="type">int</span> flags)</span>;</span><br></pre></td></tr></table></figure><p>读操作:</p><p>每次成功的 read(2) 操作都会返回一个 8 字节的整数。如果提供的缓冲区大小小于 8 字节,则 read(2) 会失败并返回错误 EINVAL。</p><p>read(2) 返回的值采用主机字节序,即主机上用于整数的本机字节序。</p><p>read(2) 的行为取决于 eventfd 计数器当前是否具有非零值以及创建 eventfd 文件描述符时是否指定了 EFD_SEMAPHORE 标志:</p><ul><li><p>如果未指定 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 会返回 8 字节的数据,其中包含该值,并将计数器的值重置为零。</p></li><li><p>如果指定了 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 会返回 8 字节的数据,其中包含值 1,并将计数器的值减 1。</p></li><li><p>如果在调用 read(2) 时 eventfd 计数器为零,则调用会阻塞,直到计数器变为非零(此时 read(2) 会按上述方式进行);如果文件描述符已被设置为非阻塞,则会失败并返回错误 EAGAIN。</p></li></ul><p>写操作:</p><p>write(2) 调用会将其缓冲区中提供的 8 字节整数值<strong>添加</strong>到计数器中。计数器中可以存储的最大值是最大无符号 64 位值减 1(即 0xfffffffffffffffe)。如果加法会导致计数器的值超过最大值,那么 write(2) 会阻塞,直到对文件描述符执行 read(2) 操作,或者如果文件描述符已被设置为非阻塞,则会失败并返回错误 EAGAIN。</p><p>liburing提供了封装</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_eventfd</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">int</span> fd)</span>;</span><br></pre></td></tr></table></figure><p><code>io_uring_register_eventfd</code> 将eventfd的文件描述符fd注册到io_uring环上,当完成队列中有事件时,会对event执行write操作</p><p>如果不再需要通知,可以调用io_uring_unregister_eventfd(3)来删除eventfd注册。 不需要eventfd参数,因为一个环只能注册一个eventfd。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> BUFF_SZ 512</span></span><br><span class="line"></span><br><span class="line"><span class="type">char</span> buff[BUFF_SZ + <span class="number">1</span>];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring</span> <span class="title">ring</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">error_exit</span><span class="params">(<span class="type">char</span> *message)</span> {</span><br><span class="line"> perror(message);</span><br><span class="line"> <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">listener_thread</span><span class="params">(<span class="type">void</span> *data)</span> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> *<span class="title">cqe</span>;</span></span><br><span class="line"> <span class="type">int</span> efd = (<span class="type">int</span>) data;</span><br><span class="line"> <span class="type">eventfd_t</span> v;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s: Waiting for completion event...\n"</span>, __FUNCTION__);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> ret = eventfd_read(efd, &v); <span class="comment">//首次调用会 block , 可读以为这有事件完成了</span></span><br><span class="line"> <span class="keyword">if</span> (ret < <span class="number">0</span>) error_exit(<span class="string">"eventfd_read"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s: Got completion event.\n"</span>, __FUNCTION__);</span><br><span class="line"></span><br><span class="line"> ret = io_uring_wait_cqe(&ring, &cqe);</span><br><span class="line"> <span class="keyword">if</span> (ret < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Error waiting for completion: %s\n"</span>,</span><br><span class="line"> strerror(-ret));</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* Now that we have the CQE, let's process it */</span></span><br><span class="line"> <span class="keyword">if</span> (cqe->res < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Error in async operation: %s\n"</span>, strerror(-cqe->res));</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Result of the operation: %d\n"</span>, cqe->res);</span><br><span class="line"> io_uring_cqe_seen(&ring, cqe);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Contents read from file:\n%s\n"</span>, buff);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">setup_io_uring</span><span class="params">(<span class="type">int</span> efd)</span> {</span><br><span class="line"> <span class="type">int</span> ret = io_uring_queue_init(<span class="number">8</span>, &ring, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (ret) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Unable to setup io_uring: %s\n"</span>, strerror(-ret));</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> io_uring_register_eventfd(&ring, efd);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">read_file_with_io_uring</span><span class="params">()</span> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> *<span class="title">sqe</span>;</span></span><br><span class="line"></span><br><span class="line"> sqe = io_uring_get_sqe(&ring);</span><br><span class="line"> <span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> fd = open(<span class="string">"/etc/passwd"</span>, O_RDONLY);</span><br><span class="line"> io_uring_prep_read(sqe, fd, buff, BUFF_SZ, <span class="number">0</span>);</span><br><span class="line"> io_uring_submit(&ring);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">pthread_t</span> t;</span><br><span class="line"> <span class="type">int</span> efd;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Create an eventfd instance */</span></span><br><span class="line"> efd = eventfd(<span class="number">0</span>, <span class="number">0</span>); <span class="comment">//创建eventfd</span></span><br><span class="line"> <span class="keyword">if</span> (efd < <span class="number">0</span>)</span><br><span class="line"> error_exit(<span class="string">"eventfd"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Create the listener thread */</span></span><br><span class="line"> pthread_create(&t, <span class="literal">NULL</span>, listener_thread, (<span class="type">void</span> *)efd);</span><br><span class="line"></span><br><span class="line"> sleep(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Setup io_uring instance and register the eventfd */</span></span><br><span class="line"> setup_io_uring(efd);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Initiate a read with io_uring */</span></span><br><span class="line"> read_file_with_io_uring();</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Wait for th listener thread to complete */</span></span><br><span class="line"> pthread_join(t, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* All done. Clean up and exit. */</span></span><br><span class="line"> io_uring_queue_exit(&ring);</span><br><span class="line"> <span class="keyword">return</span> EXIT_SUCCESS;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>参考:</p><p><a href="https://unixism.net/loti/index.html">https://unixism.net/loti/index.html</a></p><p><a href="https://zhuanlan.zhihu.com/p/361955546">浅析开源项目之io_uring - 知乎 (zhihu.com)</a></p><p><a href="https://mp.weixin.qq.com/s/1wZpFhwJR-LNkQm-QzFxRQ">图解原理|Linux I/O 神器之 io_uring (qq.com)</a></p><p><a href="https://zhuanlan.zhihu.com/p/380726590">https://zhuanlan.zhihu.com/p/380726590</a></p><p><a href="https://zhuanlan.zhihu.com/p/334658432">https://zhuanlan.zhihu.com/p/334658432</a></p>]]></content>
<tags>
<tag> read </tag>
</tags>
</entry>
<entry>
<title>网络编程实战</title>
<link href="/2024/01/16/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98/"/>
<url>/2024/01/16/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98/</url>
<content type="html"><![CDATA[<h1 id="网络编程实战"><a href="#网络编程实战" class="headerlink" title="网络编程实战"></a>网络编程实战</h1><h2 id="第四讲"><a href="#第四讲" class="headerlink" title="第四讲"></a>第四讲</h2><p>调用 connect 函数将激发 TCP 的三次握手过程,而且仅在连接建立成功或出错时才返回。其中出错返回可能有以下几种情况:</p><ol><li>三次握手无法建立,客户端发出的 SYN 包没有任何响应,于是返回 TIMEOUT 错误。这种情况比较常见的原因是对应的服务端 IP 写错。</li><li>客户端收到了 RST(复位)回答,这时候客户端会立即返回 CONNECTION REFUSED 错误。这种情况比较常见于客户端发送连接请求时的请求端口写错,因为 RST 是 TCP 在发生错误时发送的一种 TCP 分节。关闭时也会产出<code>RST</code>报文与<code>SO_LINGER</code>选项有关<br>产生 RST 的三个条件是:<br>目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器(如前所述);<br>TCP 想取消一个已有连接;<br>TCP 接收到一个根本不存在的连接上的分节。</li><li>客户发出的 SYN 包在网络上引起了”destination unreachable”,即目的不可达的错误。这种情况比较常见的原因是客户端和服务器端路由不通。</li></ol><p>为什么tcp建立连接需要三次握手解释如下<br>tcp连接的双方要确保各自的<strong>收发消息</strong>的能力都是正常的。 客户端第一次发送握手消息到服务端, 服务端接收到握手消息后把<code>ack</code>和自己的<code>syn</code>一同发送给客户端,这是第二次握手, 当客户端接收到服务端发送来的第二次握手消息后,客户端可以确认“服务端的收发能力OK,客户端的收发能力OK”,但是服务端只能确认 “客户端的发送OK,服务端的接收OK” , 所以还需要第三次握手,客户端收到服务端的第二次握手消息后,发起第三次握手消息,服务端收到客户端发送的第三次握手消息后,就能够确定“服务端的发送OK,客户端的接收OK”, 至此,客户端和服务端都能够确认自己和对方的收发能力OK,,<code>tcp</code>连接建立完成。</p><h2 id="第五讲"><a href="#第五讲" class="headerlink" title="第五讲"></a>第五讲</h2><p>阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的即</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> ret = write(sockfd , <span class="type">void</span>* buffer , <span class="type">int</span> len);</span><br></pre></td></tr></table></figure><p>当<code>sockfd</code>是<strong>阻塞状态</strong>时,只有将buffer中len字节数据放入输出缓冲区后write函数才返回。</p><p>既然缓冲区如此重要,我们可不可以把缓冲区搞得大大的,这样不就可以提高应用程序的吞吐量了么?你可以想一想这个方法可行吗?另外你可以自己总结一下,一段数据流从应用程序发送端,一直到应用程序接收端,总共经过了多少次拷贝?<br>无限大肯定是不行的,这要从为什么使用缓存这个角度考虑。内核协议栈不确定用户一次要发多少数据,如果用户来一次就发一次,如果数据多还好说,如果少了,那网络I/O很频繁,而真正发送出去的数据也不多,所以为了减少网络I/O使用了缓存的策略。但为啥不呢无限大呢,网卡一次发出去的数据报它是有一个最大长度的,所以你不管累积再多数据最后还是要分片发送的,这样一来缓冲区太大也没什么意义,而且数据传输也是有延时要求的,不可能总是在缓冲区里待着等数据,这样就总会有空出来的缓冲区存放新数据,所以无限大缓冲区也没意义,反而还浪费资源。<br>发送端,假设数据能一次性复制完,那么从用户态内存拷贝到内核态内存是一次(这里应该直接拷贝到发送换冲区了),传输层组TCP包是第二次拷贝,因为要加包头,而发送缓冲区的都是紧凑内存全是应用层数据,那么分装包就需要一次拷贝,第三次,一个TCP包封装为IP报文这里可能也会需要一次拷贝,毕竟这里走到协议栈的下一层了。</p><h2 id="第六讲"><a href="#第六讲" class="headerlink" title="第六讲"></a>第六讲</h2><p>实际上不存在<code>UDP</code>发送缓冲区,因为发往<code>UDP</code>发送缓冲区的包只要超过一定阈值(值很小)就可以发往对端。所以我们一般认为<code>UDP</code>是没有发送缓冲区的。</p><p><code>UDP</code> 报文的大小</p><p>主要影响 <code>UDP</code> 报文大小的三大因素:</p><ul><li><code>UDP</code> 协议规定报文长度为 16 位,所以 <code>UDP</code> 的报文长度不能超过 2^16 = 65536 字节</li><li>以太网(Ethernet)数据帧的长度,这是由以太网的物理特性决定,也叫数据链路层的 <code>MTU</code>(最大传输单元)</li><li>socket 的 <code>UDP</code> 发送缓冲区大小</li></ul><p><code>UDP </code>最大数据包长度</p><p>根据 <code>UDP</code> 协议,从 <code>UDP</code> 数据包的包头可以看出,<code>UDP</code> 的最大包长度是 2^16-1 个字节。用<code>sendto</code>函数最大能发送数据的长度为:<code>65535- IP头(20) - UDP头(8)=65507字节</code>。用<code>sendto</code>函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。</p><p>由于 <code>UDP</code> 包头占 8 个字节,而在 <code>IP</code>层进行封装后的 <code>IP</code> 包头占去 20 字节,所以这个是 <code>UDP</code> 数据包的最大理论长度是 2^16 - 1 - 8 - 20 = 65507 字节。</p><p>同时 <code>UDP</code> 发送缓冲区大小(<code>linux</code>下<code>UDP</code>发送缓冲区大小为:<code>cat /proc/sys/net/core/wmem_default</code>)相关,肯定不能超过缓冲区大小。</p><p><code>UDP</code> 理想数据包长度</p><p>每个以太网帧都有最小的大小 46 字节,最大不能超过 1500 字节。</p><p>除去链路层的首部和尾部的 18 个字节,链路层的数据区范围是 46-1500 字节,</p><p>那么链路层的数据区,即 <code>MTU</code>(最大传输单元)为 1500 字节。</p><p>事实上这个 1500 字节就是网络层 <code>IP</code> 数据报的长度限制。</p><p>因为 <code>IP</code> 数据报的首部为 20 字节,所以 <code>IP</code> 数据报的数据区长度最大为 1480 字节。而这个 1480 字节就是用来放 TCP 传来的 <code>TCP</code> 报文段或 <code>UDP</code> 传来的 <code>UDP</code> 数据报的。</p><p>除去 <code>UDP</code> 包头占 8 个 字节,那么 <code>UDP</code> 数据报的数据区最大长度为 1472 字节。</p><p><strong>结论1:局域网环境下,建议将 <code>UDP</code> 数据控制在 1472 字节以下</strong></p><p>Unix 网络编程第一卷里说:<code>ipv4</code> 协议规定 <code>ip</code> 层的最小重组缓冲区大小为 576 字节,所以将 <code>UDP</code> 数据报的数据区最大长度控制在 548 字节(576-8-20)以内。</p><p><strong>结论2:<code>Internet </code>编程时,建议将 <code>UDP</code> 数据控制在 548 字节以下</strong></p><h2 id="第十讲"><a href="#第十讲" class="headerlink" title="第十讲"></a>第十讲</h2><p>TIME_WAIT的作用: 1. 确保主动断开方的最后一个ACK成功发到对方 2. 确保残留的TCP包自然消亡</p><p>优化<code>TIME_WAIT</code>,可以通过设置套接字选项来设置调用close或shutdown关闭连接时的行为</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">setsockopt</span><span class="params">(<span class="type">int</span> sockfd, <span class="type">int</span> level, <span class="type">int</span> optname, <span class="type">const</span> <span class="type">void</span> *optval,<span class="type">socklen_t</span> optlen)</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> {</span> <span class="comment">//linger 英文停留</span></span><br><span class="line"> <span class="type">int</span> l_onoff; <span class="comment">/* 0=off, nonzero=on */</span></span><br><span class="line"> <span class="type">int</span> l_linger; <span class="comment">/* linger time, POSIX specifies units as seconds */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>设置 linger 参数有几种可能:</p><ul><li><p>如果<code>l_onoff</code>为 0,那么关闭本选项。l_linger的值被忽略,这对应了默认行为,<code>close</code> 或 <code>shutdown</code> 立即返回。如果在套接字发送缓冲区中有数据残留,系统会将试着把这些数据发送出去。</p></li><li><p>如果<code>l_onoff</code>为非 0, 且<code>l_linger</code>值也为 0,那么调用 <code>close</code> 后,会立该发送一个 <code>RST</code> 标志给对端,该 <code>TCP</code> 连接将跳过四次挥手,也就<strong>跳过了 <code>TIME_WAIT</code> 状态,直接关闭</strong>。这种关闭的方式称为“强行关闭”。 在这种情况下,排队数据不会被发送,被动关闭方也不知道对端已经彻底断开。只有当被动关闭方正阻塞在<code>recv()</code>调用上时,接受到 <code>RST</code> 时,会立刻得到一个<code>“connet reset by peer”</code>的异常。</p></li><li><p>如果<code>l_onoff</code>为非 0, 且<code>l_linger</code>的值也非 0,那么调用 <code>close</code> 后,调用 <code>close</code> 的线程就将阻塞,直到数据被发送出去,或者设置的<code>l_linger</code>计时时间到。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> <span class="title">so_linger</span>;</span></span><br><span class="line">so_linger.l_onoff = <span class="number">1</span>;</span><br><span class="line">so_linger.l_linger = <span class="number">1</span>;</span><br><span class="line">setsockopt(s,SOL_SOCKET,SO_LINGER, &so_linger,<span class="keyword">sizeof</span>(so_linger));</span><br></pre></td></tr></table></figure></li></ul><p>对于设置端口重用选项 SO_REUSEADDR 并不是用于解决 TIME_WAIT 状态,而是告诉内核即使是TIME_WAIT状态的套接字,也可以将它继续使用作为新的套接字使用</p><h2 id="第十一讲"><a href="#第十一讲" class="headerlink" title="第十一讲"></a>第十一讲</h2><p>close 函数具体是如何关闭两个方向的数据流呢?<br>在输入方向,系统内核会将该套接字设置为<strong>不可读</strong>,任何读操作都会返回异常。<br>在输出方向,系统内核尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文,接下来如果再对该套接字进行写操作会返回异常。<br>如果对端没有检测到套接字已关闭,还继续发送报文,就会收到一个 RST 报文</p><p>由于close将输入设置为不可读,当服务端要做耗时任务时,由于客户端调用close()导致输入方向不可读,此时服务端运算完成返回tcp报文,但是客户端socket不可读,故内核协议栈回复RST报文</p><p>关于signal函数:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">sighandler_t</span> <span class="title function_">signal</span><span class="params">(<span class="type">int</span> signum, <span class="type">sighandler_t</span> handler)</span>;</span><br></pre></td></tr></table></figure><ul><li>如果处理方式设置为 <code>SIG_IGN</code>,则信号被忽略。 </li><li>如果处理方式设置为 <code>SIG_DFL</code>,则与信号相关的默认操作(参考 signal(7))发生。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> SIG_DFL ((__sighandler_t) 0) <span class="comment">/* Default action. */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SIG_IGN ((__sighandler_t) 1) <span class="comment">/* Ignore signal. */</span></span></span><br></pre></td></tr></table></figure><blockquote><p>你可以看到在今天的服务器端程序中,直接调用exit(0)完成了 FIN 报文的发送,这是为什么呢?为什么不调用 close 函数或 shutdown 函数呢?</p></blockquote><p>因为在调用exit之后进程会退出,而进程相关的所有的资源,文件,内存,信号等内核分配的资源都会被释放,在<code>linux</code>中,一切皆文件,本身socket就是一种文件类型,内核会为每一个打开的文件创建<code>file</code>结构并维护指向改结构的引用计数,每一个进程结构中都会维护本进程打开的文件数组,数组下标就是<code>fd</code>,内容就指向上面的<code>file</code>结构,<code>close</code>本身就可以用来操作所有的文件,做的事就是,删除本进程打开的文件数组中指定的<code>fd</code>项,并把指向的<code>file</code>结构中的引用计数减一,等引用计数为 0 的时候,就会调用内部包含的文件操作<code>close</code>,针对于<code>socket</code>,它内部的实现就是调用<code>shutdown</code>,只是参数是关闭读写端,从而比较粗暴的关闭连接。</p><h2 id="第十二讲"><a href="#第十二讲" class="headerlink" title="第十二讲"></a>第十二讲</h2><p>socket设置保活选项</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">Socket::setKeepAlive</span><span class="params">(<span class="type">bool</span> on)</span> <span class="type">const</span> {</span><br><span class="line"> <span class="type">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line"> ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,</span><br><span class="line"> &optval, static_cast<<span class="type">socklen_t</span>>(<span class="keyword">sizeof</span> optval));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。</p><p>上述的可定义变量,分别被称为保活时间、保活时间间隔和保活探测次数。在 <code>Linux</code> 系统中,这些变量分别对应 <code>sysctl</code> 变量<code>net.ipv4.tcp_keepalive_time</code>、<code>net.ipv4.tcp_keepalive_intvl</code>、 <code>net.ipv4.tcp_keepalve_probes</code>,默认设置是 7200 秒(2 小时)、75 秒和 9 次探测。</p><p>由于TCP自身的<code>KeepAlive</code>机制所需的时间太长,对很多对时延要求敏感的系统中,这个时间间隔是不可接受的。所以通常自实现心跳机制</p><h2 id="第十三讲"><a href="#第十三讲" class="headerlink" title="第十三讲"></a>第十三讲</h2><p>Nagle算法 和 延迟ACK 的组合:</p><p>客户端分两次将一个请求发送出去,由于请求的第一部分的报文未被确认,Nagle 算法开始起作用;同时延时 ACK 在服务器端起作用,假设延时时间为 200ms,服务器等待 200ms 后,对请求的第一部分进行确认;接下来客户端收到了确认后,Nagle 算法解除请求第二部分的阻止,让第二部分得以发送出去,服务器端在收到之后,进行处理应答,同时将第二部分的确认捎带发送出去。</p><p><img src="/img/Snipaste_2023-12-18_22-51-37.png"></p><p>Nagle 算法和延时确认组合在一起,增大了处理时延,实际上,两个优化彼此在阻止对方。从上面的例子可以看到,在有些情况下 Nagle 算法并不适用, 比如对时延敏感的应用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> on = <span class="number">1</span>; <span class="comment">//关闭 Nagle 算法</span></span><br><span class="line">setsockopt(sock , IPPROTO_TCP , TCP_NODELAY , (vodi*)&on , <span class="keyword">sizeof</span>(on));</span><br></pre></td></tr></table></figure><p>值得注意的是,除非我们对此有十足的把握,否则不要轻易改变默认的 TCP Nagle 算法。因为在现代操作系统中,针对 Nagle 算法和延时 ACK 的优化已经非常成熟了,有可能在禁用 Nagle 算法之后,性能问题反而更加严重。</p><h2 id="第十五讲"><a href="#第十五讲" class="headerlink" title="第十五讲"></a>第十五讲</h2><p>重用套接字选项,通过给套接字配置可重用属性,告诉操作系统内核,这样的 TCP 连接完全可以复用 TIME_WAIT 状态的连接</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> on = <span class="number">1</span>;</span><br><span class="line">setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, <span class="keyword">sizeof</span>(on));</span><br></pre></td></tr></table></figure><p><code>SO_REUSEADDR</code> 套接字选项还有一个作用,那就是本机服务器如果有多个地址(ip地址),可以在不同地址上使用相同的端口提供服务。</p><p>要在创建socket和bind之间设置 <code>SO_REUSEADDR</code> 套接字选项 因为<code>SO_REUSEADDR</code> 是针对新建立的连接才起作用,对已建立的连接设置是无效的。</p><h2 id="第十七讲"><a href="#第十七讲" class="headerlink" title="第十七讲"></a>第十七讲</h2><p><img src="/img/Snipaste_2023-12-27_23-10-09.png"></p><ul><li>网络中断造成的对端无 FIN 包</li></ul><p>很多原因都会造成网络中断,在这种情况下,TCP 程序并不能及时感知到异常信息。除非网络中的其他设备,如路由器发出一条 ICMP 报文,说明目的网络或主机不可达,这个时候通过 read 或 write 调用就会返回 Unreachable 的错误。</p><p>大多数时候并不是如此,在没有 ICMP 报文的情况下,TCP 程序并不能理解感应到连接异常。如果程序是阻塞在 read 调用上,那么很不幸,程序无法从异常中恢复。</p><p>如果程序先调用了 write 操作发送了一段数据流,接下来阻塞在 read 调用上,结果会非常不同。Linux 系统的 TCP 协议栈会不断尝试将发送缓冲区的数据发送出去,大概在重传 12 次、合计时间约为 9 分钟之后,协议栈会标识该连接异常,这时,阻塞的 read 调用会返回一条 TIMEOUT 的错误信息。如果此时程序还执着地往这条连接写数据,写操作会立即失败,返回一个 SIGPIPE 信号给应用程序。</p><ul><li>系统崩溃造成的对端无 FIN 包</li></ul><p>当系统突然崩溃,如断电时,网络连接上来不及发出任何东西。这里和通过系统调用杀死应用程序非常不同的是,没有任何 FIN 包被发送出来。</p><p>在没有 ICMP 报文的情况下,TCP 程序只能通过 read 和 write 调用得到网络连接异常的信息,超时错误是一个常见的结果。</p><p>系统在崩溃之后又重启,当重传的 TCP 分组到达重启后的系统,由于系统中没有该 TCP 分组对应的连接数据,系统会返回一个 RST 重置分节,TCP 程序通过 read 或 write 调用可以分别对 RST 进行错误处理。</p><p>如果是阻塞的 read 调用,会立即返回一个错误,错误信息为连接重置(Connection Reset)。</p><p>如果是一次 write 操作,也会立即失败,应用程序会被返回一个 SIGPIPE 信号。</p><ul><li>对端有FIN包发出</li></ul><p>对端如果有 FIN 包发出,可能的场景是对端调用了 close 或 shutdown 显式地关闭了连接,也可能是对端应用程序崩溃,操作系统内核代为清理所发出的。<strong>从应用程序角度上看,无法区分是哪种情形</strong>。</p><h2 id="第十八讲"><a href="#第十八讲" class="headerlink" title="第十八讲"></a>第十八讲</h2><p>当服务器完全崩溃或网络故障,如果采用阻塞读,将无法感知到套接字异常,将会一直阻塞,可以为<code>read</code>设置超时,果超过了一段时间就认为连接已经不存在</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>;</span></span><br><span class="line">tv.tv_sec = <span class="number">5</span>;</span><br><span class="line">tv.tv_usec = <span class="number">0</span>;</span><br><span class="line">setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, (<span class="type">const</span> <span class="type">char</span> *) &tv, <span class="keyword">sizeof</span> tv);</span><br><span class="line"><span class="type">int</span> nBytes = recv(connfd, buffer, <span class="keyword">sizeof</span>(buffer), <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (nBytes == <span class="number">-1</span>) </span><br><span class="line"> <span class="keyword">if</span> (errno == EAGAIN || errno == EWOULDBLOCK) { ... } <span class="comment">//执行超时处理,如断开连接</span></span><br></pre></td></tr></table></figure><h2 id="第十九讲"><a href="#第十九讲" class="headerlink" title="第十九讲"></a>第十九讲</h2><p>一个进程无论是正常退出(exit 或者 main 函数返回),还是非正常退出(比如,收到 SIGKILL 信号关闭,就是我们常常干的 kill -9),所有该进程打开的描述符都会被系统关闭,这也导致 TCP 描述符对应的连接上发出一个 FIN 包。</p><h2 id="第二十讲"><a href="#第二十讲" class="headerlink" title="第二十讲"></a>第二十讲</h2><p>我们可以使用 fgets 方法等待标准输入,但是一旦这样做,就没有办法在套接字有数据的时候读出数据;我们也可以使用 read 方法等待套接字有数据返回,但是这样做,也没有办法在标准输入有数据的情况下,读入数据并发送给对方。I/O 多路复用的设计初衷就是解决这样的场景。我们可以把标准输入、套接字等都看做 I/O 的一路,多路复用的意思,就是在任何一路 I/O 有“事件”发生的情况下,通知应用程序去处理相应的 I/O 事件,这样我们的程序就变成了“多面手”,在同一时刻仿佛可以处理多个 I/O 事件。select所支持的文件描述符上线只有1024个</p><blockquote><p>你认为 select 函数里一定需要传入描述字基数这个值么?</p></blockquote><p>需要设置。<code>int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);</code> 函数select检测相当于遍历三个 fd_set,需要知道数组的上限</p><h2 id="第二十一讲"><a href="#第二十一讲" class="headerlink" title="第二十一讲"></a>第二十一讲</h2><p>poll 突破了select对文件描述符的限制</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">poll</span><span class="params">(<span class="keyword">struct</span> pollfd *fds, <span class="type">unsigned</span> <span class="type">long</span> nfds, <span class="type">int</span> timeout)</span>; </span><br></pre></td></tr></table></figure><ul><li><p>pollfd 数组:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> {</span></span><br><span class="line"> <span class="type">int</span> fd; <span class="comment">/* file descriptor */</span></span><br><span class="line"> <span class="type">short</span> events; <span class="comment">/* events to look for POLLIN POOLOUT*/</span></span><br><span class="line"> <span class="type">short</span> revents; <span class="comment">/* events returned */</span></span><br><span class="line"> };</span><br><span class="line">其中对应的事件:</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLIN 0x0001 <span class="comment">/* any readable data available */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLPRI 0x0002 <span class="comment">/* OOB/Urgent readable data */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLRDNORM 0x0040 <span class="comment">/* non-OOB/URG data available */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLRDBAND 0x0080 <span class="comment">/* OOB/Urgent readable data */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLOUT 0x0004 <span class="comment">/* file descriptor is writeable */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLWRNORM POLLOUT <span class="comment">/* no write type differentiation */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLWRBAND 0x0100 <span class="comment">/* OOB/Urgent data can be written */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLERR 0x0008 <span class="comment">/* 一些错误发送 */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLHUP 0x0010 <span class="comment">/* 描述符挂起*/</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLNVAL 0x0020 <span class="comment">/* 请求的事件无效*/</span></span></span><br></pre></td></tr></table></figure></li></ul><p>如果我们<strong>不想对某个 pollfd 结构进行事件检测,</strong>可以把它对应的 pollfd 结构的 fd 成员设置成一个负值。这样,poll 函数将忽略这样的 events 事件,检测完成以后,所对应的“returned events”的成员值也将设置为 0。在 poll 函数里,我们可以控制 pollfd 结构的数组大小,这意味着我们可以突破原来 select 函数最大描述符的限制,在这种情况下,应用程序调用者需要分配 pollfd 数组并通知 poll 函数该数组的大小。</p><h2 id="第二十二讲"><a href="#第二十二讲" class="headerlink" title="第二十二讲"></a>第二十二讲</h2><p>read / write:</p><p>非阻塞读操作:如果套接字对应的接收缓冲区没有数据可读,在非阻塞情况下 read 调用会立即返回,一般返回 EWOULDBLOCK 或 EAGAIN 出错信息</p><p>非阻塞写操作:在非阻塞 I/O 的情况下,如果套接字的发送缓冲区已达到了极限,不能容纳更多的字节,那么操作系统内核会<strong>尽最大可能</strong>从应用程序拷贝数据到发送缓冲区中,并立即从 write 等函数调用中返回已拷贝的字节数</p><p>accept:</p><p>当 accept 和 I/O 多路复用 select、poll 等一起配合使用时,如果在监听套接字上触发事件,说明有连接建立完成,此时调用 accept 肯定可以返回已连接套接字。但是总有例外</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//客户端</span></span><br><span class="line">connect(); <span class="comment">//在收到服务端回的ack时返回,进入establish状态</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> <span class="title">ling</span>;</span></span><br><span class="line">ling.l_onoff = <span class="number">1</span>; </span><br><span class="line">ling.l_linger = <span class="number">0</span>;</span><br><span class="line">setsockopt(socket_fd, SOL_SOCKET, SO_LINGER, &ling, <span class="keyword">sizeof</span>(ling));</span><br><span class="line">close(socket_fd); <span class="comment">//此时服务端没有调用accept,就受到了RST报文</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//服务端</span></span><br><span class="line"><span class="keyword">if</span> (FD_ISSET(listen_fd, &readset)) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"listening socket readable\n"</span>);</span><br><span class="line"> sleep(<span class="number">5</span>); <span class="comment">//时延 </span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_storage</span> <span class="title">ss</span>;</span></span><br><span class="line"> <span class="type">socklen_t</span> slen = <span class="keyword">sizeof</span>(ss);</span><br><span class="line"> <span class="type">int</span> fd = accept(listen_fd, (<span class="keyword">struct</span> sockaddr *) &ss, &slen);</span><br></pre></td></tr></table></figure><p>这里的休眠时间非常关键,这样,在监听套接字上有可读事件发生时,并没有马上调用 accept。由于客户端发生了 RST 分节,该连接被接收端内核从自己的已完成队列中删除了,此时再调用 accept,由于没有已完成连接(假设没有其他已完成连接),accept 一直阻塞,更为严重的是,该线程再也没有机会对其他 I/O 事件进行分发,相当于该服务器无法对其他 I/O 进行服务。如果我们将监听套接字设为非阻塞,上述的情形就不会再发生。只不过对于 accept 的返回值,需要正确地处理各种看似异常的错误,例如忽略 EWOULDBLOCK、EAGAIN 等。</p><p>connect:</p><p>非阻塞调用时会立即返回 EINPROGRESS 错误,连接后会返回 EISCONN 错误</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">if</span>(connect() == EISCONN)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="第二十三讲"><a href="#第二十三讲" class="headerlink" title="第二十三讲"></a>第二十三讲</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> {</span></span><br><span class="line"> <span class="type">uint32_t</span> events; <span class="comment">/* Epoll events */</span></span><br><span class="line"> <span class="type">epoll_data_t</span> data; <span class="comment">/* User data variable */</span></span><br><span class="line"> };</span><br><span class="line">* EPOLLIN:表示对应的文件描述字可以读;</span><br><span class="line"></span><br><span class="line">* EPOLLOUT:表示对应的文件描述字可以写;</span><br><span class="line"></span><br><span class="line">* EPOLLRDHUP:表示套接字的一端已经关闭,或者半关闭;</span><br><span class="line"></span><br><span class="line">* EPOLLHUP:表示对应的文件描述字被挂起;</span><br><span class="line"></span><br><span class="line">* EPOLLET:设置为 edge-triggered,默认为 level-triggered。</span><br></pre></td></tr></table></figure><p>水平触发(level-trggered)</p><ul><li>只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知,</li><li>当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知</li></ul><p>边缘触发(edge-triggered)</p><ul><li>当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知,</li><li>当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知</li></ul><p>在linux下,如果用边缘触发同时注册了读和写,当读触发的时候,内核向用户返回fd的时候同时会检查fd是否符合可写的条件(有空间容纳待写入的数据),如果满足可写的条件,同时会加上EPOLLOUT标记。</p><h2 id="第三十讲"><a href="#第三十讲" class="headerlink" title="第三十讲"></a>第三十讲</h2><p>无论是阻塞 I/O,还是阻塞 I/O,和基于非阻塞 I/O 的多路复用都是<strong>同步调用技术。为什么这么说呢?因为同步调用、异步调用的说法,是对于获取数据的过程而言的,前面几种最后获取数据的 read 操作调用,都是同步的,在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,这个过程是在 read 函数中同步进行的,如果内核实现的拷贝效率很差,read 调用就会在这个同步过程中消耗比较长的时间</strong></p><p>而真正的异步调用则不用担心这个问题,我们接下来就来介绍第四种 I/O 技术,当我们发起 io_uring之后,就立即返回,内核自动将数据从内核空间拷贝到应用程序空间,这个拷贝过程是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。</p><p><a href="https://juejin.cn/post/6844903879688060942">https://juejin.cn/post/6844903879688060942</a> 重置报文</p>]]></content>
<tags>
<tag> 网络编程 </tag>
</tags>
</entry>
<entry>
<title>share_ptr和weak_ptr</title>
<link href="/2024/01/15/share_ptr%E4%B8%8Eweak_ptr/"/>
<url>/2024/01/15/share_ptr%E4%B8%8Eweak_ptr/</url>
<content type="html"><![CDATA[<h1 id="share-ptr和weak-ptr"><a href="#share-ptr和weak-ptr" class="headerlink" title="share_ptr和weak_ptr"></a>share_ptr和weak_ptr</h1><h2 id="share-ptr"><a href="#share-ptr" class="headerlink" title="share_ptr"></a>share_ptr</h2><h3 id="内存模型"><a href="#内存模型" class="headerlink" title="内存模型"></a>内存模型</h3><p>shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。</p><p><img src="/img/share_ptr%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png"></p><p>当执行:<code>share_ptr<int> p1(new int(1)); share_ptr<int> p2 = p1</code> 时对应的share_ptr结构中指针将指向同一个对象以及控制块</p><p><img src="/img/share_ptr%E8%B5%8B%E5%80%BC.png"></p><p>std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。</p><p>share_ptr的线程安全问题: </p><blockquote><p><strong>引用计数是线程安全的</strong>,引用计数使用了原子类型,指向对象数据,如果发生修改,将不是线程安全的,若要数据安全,要在对象数据访问上增加锁机制保证对象的数据安全</p></blockquote><h3 id="基本用法,常用函数"><a href="#基本用法,常用函数" class="headerlink" title="基本用法,常用函数"></a>基本用法,常用函数</h3><p>只能通过复制构造或复制赋值其值给另一 <code>shared_ptr</code> ,将对象所有权与另一 <code>shared_ptr</code> 共享。用另一 <code>shared_ptr</code> 所占有的底层指针创建新的 <code>shared_ptr</code> 导致未定义行为。</p><p><code>std::shared_ptr</code> 可以用于不完整类型T 。然而,参数为裸指针的构造函数( template<class Y> shared_ptr(Y * ) )和 template<class Y> void reset(Y*) 成员函数只可以用指向完整类型的指针调用(注意 <a href="https://www.apiref.com/cpp-zh/cpp/memory/unique_ptr.html">std::unique_ptr</a> 可以从指向不完整类型的裸指针构造)。</p><ul><li>通过构造函数,<code>make_shared()</code> ,<code>reset()</code>方法来初始化<code>shared_ptr</code></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p1</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">1</span>))</span></span>;</span><br><span class="line">std::shared_ptr<<span class="type">int</span>> p2 = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">100</span>);</span><br><span class="line">std::shared_ptr<<span class="type">int</span>> p3;</span><br><span class="line">p3.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">int</span>(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>不能将一个原始指针直接赋值给一个智能指针<code>std::shared_ptr<int> p = new int(10)</code>这种写法将无法编译,shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数或辅助方法来初始化,因为<code>template< class Y > explicit shared_ptr( Y* ptr );</code>参数类型是指针的构造函数被explict修饰(指定构造函数或转换函数 (C++11 起)或推导指引显式,<strong>即它不能用于隐式转换和复制初始化</strong></p><ul><li>智能指针可以通过重载的bool类型操作符来判断</li></ul><p>提供了<code>explicit operator bool() const noexcept;</code>检查 <code>*this</code> 是否存储非空指针,即是否有 <code>get() != nullptr</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">report</span><span class="params">(std::shared_ptr<<span class="type">int</span>> ptr)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(ptr) {</span><br><span class="line"> std::cout << *ptr <<std::endl;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> std::cout << <span class="string">"*ptr is not a valid pointer \n"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>std::shared_ptr<T>::use_count,返回引用计数</li></ul><p>返回管理当前对象的不同 <code>shared_ptr</code> 实例(包含 this )数量。若无管理对象,则返回 0。<strong>多线程环境下, use_count 返回的值是近似的</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">fun</span><span class="params">(std::shared_ptr<<span class="type">int</span>> sp)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"fun: sp.use_count() == "</span> << sp.<span class="built_in">use_count</span>() << <span class="string">'\n'</span>; </span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">auto</span> sp1 = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">5</span>);</span><br><span class="line"> std::cout << <span class="string">"sp1.use_count() == "</span> << sp1.<span class="built_in">use_count</span>() << <span class="string">'\n'</span>; </span><br><span class="line"> <span class="comment">//sp1.use_count() == 1</span></span><br><span class="line"> <span class="built_in">fun</span>(sp1);</span><br><span class="line"> <span class="comment">//fun: sp.use_count() == 2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>reset方法</li></ul><p>对于无参数reset,<code>void reset() noexcept</code>释放被管理对象的所有权,若存在。调用后, *this 不管理对象。等价于 shared_ptr().swap( *this)</p><p>对于存在一个参数的reset,<code>template< class Y >void reset( Y* ptr );</code>以 <code>ptr</code> 所指向的对象替换被管理对象,以 delete 表达式为删除器。合法的 delete 表达式必须可用,即 delete ptr 必须为良式,拥有良好定义行为且不抛任何异常。等价于 shared_ptr<T>(ptr).swap(*this); 。</p><ul><li>获取原始指针get方法</li></ul><p>当需要获取原始指针时,可以通过get方法来返回原始指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">ptr</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>{<span class="number">1</span>})</span></span>;</span><br><span class="line"><span class="type">int</span> *p = ptr.<span class="built_in">get</span>(); <span class="comment">//获取原始指针</span></span><br></pre></td></tr></table></figure><p>谨慎使用<code>p.get()</code>的返回值:</p><blockquote><p>不要保存ptr.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的 。保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针</p><p>不要delete ptr.get()的返回值 ,会导致对一块内存delete两次的错误</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> * p = <span class="keyword">new</span> <span class="built_in">int</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p1</span><span class="params">(p)</span></span>; </span><br><span class="line"> <span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p2</span><span class="params">(p1.get())</span></span>; <span class="comment">//错误将会double free</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>如上代码是错误的</strong>,<code>p1.get()</code>返回指针类型,故调用<code>template< class Y > explicit shared_ptr( Y* ptr )</code>,对于此构造函数,只是构造 <code>shared_ptr</code> ,管理 <code>ptr</code> 所指向的对象,此构造过程并不会产生共享对象,<code>shared_ptr( const shared_ptr& r ) noexcept;</code>和<code>operator=</code>会产生共享对象</p><p><strong>不要将this指针作为shared_ptr返回出来</strong>,原理同上</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">shared_ptr<A> <span class="title">GetSelf</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">shared_ptr</span><A>(<span class="keyword">this</span>); <span class="comment">// 不要这么做</span></span><br><span class="line"> }</span><br><span class="line"> ~<span class="built_in">A</span>(){ cout << <span class="string">"Destructor A"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="function">shared_ptr<A> <span class="title">sp1</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> shared_ptr<A> sp2 = sp1-><span class="built_in">GetSelf</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><strong>正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的 成员函数shared_from_this()来返回this的shared_ptr</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>: <span class="keyword">public</span> std::enable_shared_from_this<A></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">shared_ptr<A> <span class="title">GetSelf</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="built_in">A</span>(){cout << <span class="string">"Destructor A"</span> << endl;}</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">shared_ptr<A> <span class="title">sp1</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> shared_ptr<A> sp2 = sp1-><span class="built_in">GetSelf</span>(); <span class="comment">// ok</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><ul><li>不要在函数实参中创建 <code>share_ptr</code></li></ul><p>如:<code> function(std::shared_ptr<int> (new int(10)) , g() )</code>因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也 可能从左到右,所以,可能的过程是先<code>new int</code>,然后调用<code>g()</code>,如果恰好<code>g()</code>发生异常,而<code>shared_ptr</code>还没有创建, 则<code>int</code>内存泄漏了,正确的写法应该是先创建智能指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">shared_ptr<<span class="type">int</span>> <span class="title">p</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>)</span></span>;</span><br><span class="line"><span class="built_in">function</span>(p, <span class="built_in">g</span>());</span><br></pre></td></tr></table></figure><ul><li>避免循环引用</li></ul><p>循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导致两个指针都不会被析构,产生内存泄漏,解决的办法是把A和B任何一个成员变量改为weak_ptr</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<B> bptr;</span><br><span class="line"> ~<span class="built_in">A</span>()</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"A is deleted"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<A> aptr;</span><br><span class="line"> ~<span class="built_in">B</span>()</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"B is deleted"</span> << endl;</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"><span class="function"></span>{</span><br><span class="line"> {</span><br><span class="line"> <span class="function">std::shared_ptr<A> <span class="title">ap</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> <span class="function">std::shared_ptr<B> <span class="title">bp</span><span class="params">(<span class="keyword">new</span> B)</span></span>;</span><br><span class="line"> ap->bptr = bp;</span><br><span class="line"> bp->aptr = ap;</span><br><span class="line"> }</span><br><span class="line"> cout << <span class="string">"main leave"</span> << endl; <span class="comment">// 循环引用导致ap bp退出了作用域都没有析构</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><h2 id="weak-ptr"><a href="#weak-ptr" class="headerlink" title="weak_ptr"></a>weak_ptr</h2><p><code>weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 </code>Shared_ptr<code> 管理的对象. 进行该对象的内存管理的是那个强引用的</code>shared_ptr<code>,</code> weak_ptr`只是提供了对管理对象的一个访问手段。</p><h3 id="基本用法:"><a href="#基本用法:" class="headerlink" title="基本用法:"></a>基本用法:</h3><ul><li>通过use_count()方法获取当前观察资源的引用计数</li></ul><p>返回共享被管理对象所有权的 <code>shared_ptr</code> 实例数量,或 0 ,若被管理对象已被删除,即 <code>*this</code> 为空。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">shared_ptr<<span class="type">int</span>> <span class="title">sp</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line"> weak_ptr<<span class="type">int</span>> wp = sp;</span><br><span class="line"> cout << wp.<span class="built_in">use_count</span>() <<endl; <span class="comment">//1</span></span><br><span class="line"> shared_ptr<<span class="type">int</span>> sp1 = sp;</span><br><span class="line"> cout <<wp.<span class="built_in">use_count</span>() << endl; <span class="comment">//2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>通过expired()方法判断所观察资源是否已经释放</li></ul><p><code>bool expired() const noexcept;</code>等价于 <code>use_count() == 0</code> 。可能仍未对被管理对象调用析构函数,但此对象的析构已经临近(或可能已发生),若被管理对象已被删除则为 true ,否则为 false 。若被管理对象在线程间共享,则此函数内在地不可靠,通常 false 结果可能在能用之前就变得过时。 true 结果可靠。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">shared_ptr<<span class="type">int</span>> <span class="title">sp</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line"><span class="function">weak_ptr<<span class="type">int</span>> <span class="title">wp</span><span class="params">(sp)</span></span>;</span><br><span class="line"><span class="keyword">if</span>(wp.<span class="built_in">expired</span>())</span><br><span class="line"> cout << <span class="string">"weak_ptr无效,资源已释放"</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> cout << <span class="string">"weak_ptr有效"</span>;</span><br></pre></td></tr></table></figure><ul><li>通过lock方法获取监视的shared_ptr</li></ul><p><code>std::shared_ptr<T> lock() const noexcept</code>,创建新的 <code>std::shared_ptr</code> 对象,它共享被管理对象的所有权。若无被管理对象,即 <code>*this</code> 为空,则返回亦为<code>nullptr</code>的 <code>shared_ptr</code>,等效地返回 <code>expired() ? shared_ptr<T>() : shared_ptr<T>(*this)</code> ,<strong>原子地执行</strong></p><p>在多线程环境下:</p><p>线程一:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">std::weak_ptr<<span class="type">int</span>> gw;</span><br><span class="line">gw = sp;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> spt = gw.<span class="built_in">lock</span>();</span><br><span class="line"> <span class="keyword">if</span> ( gw.<span class="built_in">expired</span>() ) {</span><br><span class="line"> cout << <span class="string">" gw Invalid , resource released"</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">" gw Vaild , *spt = "</span> << *spt;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>线程二:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::shared_ptr<<span class="type">int</span>> sp = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>在线程二中资源有可能释放,为了保证线程一不出错,要使用weak_ptr观察,要<strong>先上锁后检查</strong>,根据加锁情况分类</p><ul><li>weak_ptr解决循环引用</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::weak_ptr<B> bptr; <span class="comment">// 修改为weak_ptr</span></span><br><span class="line"> ~<span class="built_in">A</span>()</span><br><span class="line"> { cout << <span class="string">"A is deleted"</span> << endl;}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<A> aptr;</span><br><span class="line"> ~<span class="built_in">B</span>()</span><br><span class="line"> { cout << <span class="string">"B is deleted"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> <span class="function">std::shared_ptr<A> <span class="title">ap</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> <span class="function">std::shared_ptr<B> <span class="title">bp</span><span class="params">(<span class="keyword">new</span> B)</span></span>;</span><br><span class="line"> ap->bptr = bp;</span><br><span class="line"> bp->aptr = ap;</span><br><span class="line"> }</span><br><span class="line"> cout << <span class="string">"main leave"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样在对B的成员赋值时,即执行<code>bp->aptr=ap;</code>时,由于<code>aptr</code>是<code>weak_ptr</code>,它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,ap的引用计数为减为0,A指针会被析构,析构后其内部的<code>bptr</code>的引用计数会被减为1,然后在离开作用域后<code>bp</code>引用计数又从1减为0,B对象也被析构,不会发生内存泄漏</p><p><a href="https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html">https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html</a> 多线程环境下share_ptr写时加锁原因</p>]]></content>
<tags>
<tag> c++ </tag>
</tags>
</entry>
</search>