📄 item_013.htm
字号:
mso-hansi-font-family:"Times New Roman"'>这种自动化易于实现,优雅,成本低,并且不容易出错。如果你决定不用这种方法,那么你就选择了手工管理,这不是那么简单,需要高度注意以保证调用是成对的,包括在有分支控制流和异常的情况下也是如此。在</span><span
lang=EN-US>C++</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>能够通过易用的</span><span lang=EN-US>RAII</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>来提供直接的自动化时,继续坚持这种</span><span lang=EN-US>C</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>风格的对资源分配的微管理是不可接受的。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>在处理需要调用成对的获取</span><span lang=EN-US>/</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>释放函数的资源时,请将该资源封装在对象中,并在对象的析构函数中释放该资源,这样就保证了获取</span><span
lang=EN-US>/</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>释放的匹配。例如,为了避免直接调用</span><span
class=SpellE><b style='mso-bidi-font-weight:normal'><span lang=EN-US>OpenPort</span></b></span><span
lang=EN-US> / <span class=SpellE><b style='mso-bidi-font-weight:normal'>ClosePort</b></span></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>这对非成员函数,可以考虑:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span><span
class=GramE>class</span> Port {</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span class=GramE><span
lang=EN-US>public</span></span><span lang=EN-US>:</span></p>
<p class=MsoNormal style='text-indent:36.0pt;tab-stops:216.0pt'><span
lang=EN-US><span style='mso-spacerun:yes'> </span>Port( const string&
destination );<span style='mso-tab-count:1'> </span><i
style='mso-bidi-font-style:normal'>// </i></span><i style='mso-bidi-font-style:
normal'><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>调用</span><span class=SpellE><span
lang=EN-US>OpenPort</span></span><span lang=EN-US><o:p></o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt;tab-stops:216.0pt'><span
lang=EN-US><span style='mso-spacerun:yes'> </span>~Port();<span
style='mso-tab-count:1'> </span><i
style='mso-bidi-font-style:normal'>// </i></span><i style='mso-bidi-font-style:
normal'><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>调用</span><span class=SpellE><span
lang=EN-US>ClosePort</span></span><span lang=EN-US><o:p></o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span><i style='mso-bidi-font-style:normal'>//
… </i></span><i style='mso-bidi-font-style:normal'><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>端口通常不能被复制,因此禁用复制和赋值</span><span
lang=EN-US> …<o:p></o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US>};</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span class=GramE><span
lang=EN-US>void</span></span><span lang=EN-US> <span class=SpellE>DoSomething</span>()
{</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span>Port <span class=GramE>port1(</span>
"server1:80" );</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US><span
style='mso-spacerun:yes'> </span><i style='mso-bidi-font-style:normal'>//
…<o:p></o:p></i></span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US>} <i
style='mso-bidi-font-style:normal'>// </i></span><i style='mso-bidi-font-style:
normal'><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>不可能忘记关闭端口,因为它是在作用域结束处自动关闭的</span><span
lang=EN-US><o:p></o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt'><i style='mso-bidi-font-style:
normal'><span lang=EN-US><o:p> </o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt;tab-stops:216.0pt'><span
class=SpellE><span lang=EN-US>shared_ptr</span></span><span lang=EN-US><Port>
port2 = <i style='mso-bidi-font-style:normal'>/*…*/;<span
style='mso-spacerun:yes'> </span><span style='mso-tab-count:1'> </span>//
port2</i></span><i style='mso-bidi-font-style:normal'><span style='font-family:
宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>在最后一个引用它的</span><span
lang=EN-US><o:p></o:p></span></i></p>
<p class=MsoNormal style='text-indent:36.0pt;tab-stops:216.0pt'><i
style='mso-bidi-font-style:normal'><span lang=EN-US><span style='mso-tab-count:
1'> </span>//
<span class=SpellE>shared_ptr</span></span></i><i style='mso-bidi-font-style:
normal'><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>消失后自动关闭</span><span lang=EN-US><o:p></o:p></span></i></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>也可以使用那些已经实现了该模式的库(参见</span><span
lang=EN-US>[Alexandrescu00c]</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)。在实现</span><span
lang=EN-US>RAII</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>时,请留意复制构造函数和赋值操作符(参见第</span><span
lang=EN-US>49</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条);编译器产生的版本可能并不正确。如果复制操作是不合理的,那么请把这两个操作声明为私有的,并且不要定义(参见第</span><span
lang=EN-US>53</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条),这样就可以禁用它们。如果不是这样,请在构造函数中将资源复制一份,或者对使用的数量进行引用计数。对赋值操作符也是如此。另外如果有必要,那么还要保证对象释放它原先占有的资源。一个常见的疏忽就是在新资源还没有被成功地复制(参见第</span><span
lang=EN-US>71</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条)时就释放已有的资源。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>确保所有的资源都为对象所占有。最好用智能指针来保存动态分配的资源,而不要用原始指针。此外,每个资源分配操作(如</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>new</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>)应该在单独的语句中显式地进行;在该语句之后立即把资源交给资源管理对象(如</span><span
class=SpellE><b style='mso-bidi-font-weight:normal'><span lang=EN-US>shared_ptr</span></b></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>);否则的话,有可能会造成资源泄漏,因为函数参数的求值次序是未定义的(参见第</span><span
lang=EN-US>31</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条)。例如:</span></p>
<p class=MsoNormal><span lang=EN-US><span style='mso-tab-count:1'> </span><span
class=GramE>void</span> Fun( <span class=SpellE>shared_ptr</span>< Widget
> sp1, <span class=SpellE>shared_ptr</span>< Widget > sp2);</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span lang=EN-US>// ...</span></p>
<p class=MsoNormal style='text-indent:36.0pt'><span class=GramE><span
lang=EN-US>Fun(</span></span><span lang=EN-US> <span class=SpellE>shared_ptr</span><
Widget > (<b style='mso-bidi-font-weight:normal'>new Widget</b>), <span
class=SpellE>shared_ptr</span>< Widget > (<b style='mso-bidi-font-weight:
normal'>new Widget</b>) );</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>这样的代码是不安全的。</span><span lang=EN-US>C++</span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>标准给了编译器很大的余地,使它们可以改变构成函数实参的两个表达式的次序。特别是,编译器可以使两个表达式交替执行:首先为两个对象分配内存(通过调用</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>operator new</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>),然后调用两次</span><b style='mso-bidi-font-weight:normal'><span
lang=EN-US>Widget</span></b><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>构造函数。那将成为泄漏的温床,因为如果其中一个构造函数抛出异常的话,那么<i
style='mso-bidi-font-style:normal'>另一个</i>对象所占用的内存将永远无法被释放!(细节请参见</span><span
lang=EN-US>[Sutter02]</span><span style='font-family:宋体;mso-ascii-font-family:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>)</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>这个微妙的问题有一个简单的解决方案:遵循前面的建议,绝对不要在一条语句中分配一个以上的资源,每个资源分配操作(如</span><b
style='mso-bidi-font-weight:normal'><span lang=EN-US>new</span></b><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>)应该在单独的语句中显式地进行;在该语句之后立即把资源交给资源管理对象(如</span><span
class=SpellE><b style='mso-bidi-font-weight:normal'><span lang=EN-US>shared_ptr</span></b></span><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>)。例如:</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span class=SpellE><span lang=EN-US>shared_ptr</span></span><span
lang=EN-US>< <b style='mso-bidi-font-weight:normal'>Widget</b> > <span
class=GramE>sp1(</span>new Widget), sp2( new Widget );</span></p>
<p class=MsoNormal><span class=GramE><span lang=EN-US>Fun(</span></span><span
lang=EN-US> sp1, sp2 );</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>关于使用这种风格的其它好处,另请参见第</span><span
lang=EN-US>31</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>条。</span></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><b style='mso-bidi-font-weight:normal'><span
style='font-family:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:
"Times New Roman"'>例外</span></b></p>
<p class=MsoNormal><span lang=EN-US><o:p> </o:p></span></p>
<p class=MsoNormal><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>智能指针可能被滥用。如果被指向的对象只对有限的代码(例如:完全在一个类的内部,像</span><span
lang=EN-US>Tree</span><span style='font-family:宋体;mso-ascii-font-family:"Times New Roman";
mso-hansi-font-family:"Times New Roman"'>类内部用来在结点间导航的指针)可见,那么原始指针就足够了。</span></p>
</div>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -