196 lines
31 KiB
HTML
196 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>PImpl</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_language_pimpl skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading">PImpl</h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-language.html">language</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<p>“指向实现的指针”或“pImpl”是一种 C++ 编程技巧<sup id="cite_ref-1" class="reference"><a href="cpp-language-pimpl.html#cite_note-1">[1]</a></sup>,它将类的实现细节从对象表示中移除,放到一个分离的类中,并以一个不透明的指针进行访问:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co1">// widget.h(接口)</span>
|
|
<span class="kw1">class</span> widget <span class="br0">{</span>
|
|
<span class="co1">// 公开成员</span>
|
|
<span class="kw1">private</span><span class="sy4">:</span>
|
|
<span class="kw1">struct</span> impl<span class="sy4">;</span> <span class="co1">// 实现类的前置声明</span>
|
|
<span class="co1">// 一种实现示例:见下文中的其他设计选项和权衡</span>
|
|
<a href="cpp-experimental-propagate_const.html"><span class="kw2423">std::<span class="me2">experimental</span><span class="sy4">::</span><span class="me2">propagate_const</span></span></a><span class="sy1"><</span> <span class="co1">// 转发 const 的指针包装器</span>
|
|
<a href="cpp-memory-unique_ptr.html"><span class="kw733">std::<span class="me2">unique_ptr</span></span></a><span class="sy1"><</span> <span class="co1">// 唯一所有权的不透明指针</span>
|
|
impl<span class="sy1">>></span> pImpl<span class="sy4">;</span> <span class="co1">// 指向前置声明的实现类</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="co1">// widget.cpp(实现)</span>
|
|
<span class="kw1">struct</span> widget<span class="sy4">::</span><span class="me2">impl</span> <span class="br0">{</span>
|
|
<span class="co1">// 实现细节</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>此技巧用于构造拥有稳定 ABI 的 C++ 库接口,及减少编译时依赖。</p>
|
|
<h3><span class="mw-headline" id=".E8.A7.A3.E9.87.8A">解释</span></h3>
|
|
<p>因为类的私有数据成员参与其对象表示,影响大小和布局,也因为类的私有成员函数参与<a href="cpp-language-overload_resolution.html">重载决议</a>(这发生于成员访问检查之前),故对实现细节的任何更改都要求该类的所有用户重编译。</p>
|
|
<p>pImpl 打破了这种编译依赖;实现的改动不会导致重编译。结果是,若某个库在其 ABI 中使用 pImpl,则这个库的新版本可以更改实现,并且与旧版本保持 ABI 兼容。</p>
|
|
<h3><span class="mw-headline" id=".E5.BE.97.E5.A4.B1.E6.9D.83.E8.A1.A1">得失权衡</span></h3>
|
|
<p>pImpl 技法的替代方案是</p>
|
|
<ul>
|
|
<li>内联实现:私有成员和公开成员是同一类的成员</li>
|
|
<li>纯虚类(OOP 工厂):用户获得到某个轻量级或纯虚的基类的唯一指针,实现细节则处于覆盖其虚成员函数的派生类中。</li>
|
|
</ul>
|
|
<h4><span class="mw-headline" id=".E7.BC.96.E8.AF.91.E9.98.B2.E7.81.AB.E5.A2.99">编译防火墙</span></h4>
|
|
<p>简单情况下,pImpl 和工厂方法都会打破实现和类接口的用户之间的编译时依赖。工厂方法创建对虚表的一次隐藏依赖,故而对虚函数进行重排序、添加或移除都会打破 ABI。pImpl 方法则没有隐藏的依赖,然而若实现类是类模板特化,则会丧失编译防火墙的优势:接口的用户必须观测到整个模板定义,以实例化正确的特化。这种情况下的一种常见的设计方案是,以避免参数化的方式对实现进行重构,这是《C++ 核心指南》的 <a rel="nofollow" class="external text" href="https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rt-scary">T.61 勿过分参数化成员</a> 和 <a rel="nofollow" class="external text" href="https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#t84-use-a-non-template-core-implementation-to-provide-an-abi-stable-interface">T.84 使用非模板核心实现以提供 ABI 稳定的接口</a> 的另一种使用情况。</p>
|
|
<p>举例来说,以下的类模板在其私有成员或 push_back 函数体内并未使用类型 T</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">template</span><span class="sy1"><</span><span class="kw1">class</span> T<span class="sy1">></span>
|
|
<span class="kw1">class</span> ptr_vector <span class="br0">{</span>
|
|
<span class="kw4">void</span> <span class="sy2">**</span>beg, <span class="sy2">**</span>end, <span class="sy2">**</span>cap<span class="sy4">;</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> push_back<span class="br0">(</span>T<span class="sy2">*</span> p<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>end <span class="sy1">==</span> cap<span class="br0">)</span> reallocate<span class="br0">(</span>end <span class="sy2">-</span> beg <span class="sy2">+</span> <span class="nu0">1</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="sy2">*</span>end<span class="sy2">++</span> <span class="sy1">=</span> p<span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>因此,能按原样把私有成员传递给实现,而且 push_back 可以转发到同样未在接口使用 T 的实现:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co1">// 头文件(ptr_vector.h)</span>
|
|
<span class="kw1">class</span> ptr_vector_base <span class="br0">{</span>
|
|
<span class="kw1">struct</span> impl<span class="sy4">;</span> <span class="co1">// 不依赖 T</span>
|
|
<a href="cpp-memory-unique_ptr.html"><span class="kw733">std::<span class="me2">unique_ptr</span></span></a><span class="sy1"><</span>impl<span class="sy1">></span> pImpl<span class="sy4">;</span>
|
|
<span class="kw1">protected</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> push_back_fwd<span class="br0">(</span><span class="kw4">void</span><span class="sy2">*</span><span class="br0">)</span><span class="sy4">;</span>
|
|
... <span class="co1">// 见特殊成员函数的实现部分</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">template</span><span class="sy1"><</span><span class="kw1">class</span> T<span class="sy1">></span>
|
|
<span class="kw1">class</span> ptr_vector <span class="sy4">:</span> <span class="kw1">private</span> ptr_vector_base <span class="br0">{</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> push_back<span class="br0">(</span>T<span class="sy2">*</span> p<span class="br0">)</span> <span class="br0">{</span> push_back_fwd<span class="br0">(</span>p<span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="co1">// 源文件(ptr_vector.cpp)</span>
|
|
<span class="kw1">struct</span> ptr_vector_base<span class="sy4">::</span><span class="me2">impl</span> <span class="br0">{</span>
|
|
<span class="kw4">void</span> <span class="sy2">**</span>beg, <span class="sy2">**</span>end, <span class="sy2">**</span>cap<span class="sy4">;</span>
|
|
<span class="kw4">void</span> push_back<span class="br0">(</span><span class="kw4">void</span><span class="sy2">*</span> p<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>end <span class="sy1">==</span> cap<span class="br0">)</span> reallocate<span class="br0">(</span>end <span class="sy2">-</span> beg <span class="sy2">+</span> <span class="nu0">1</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="sy2">*</span>end<span class="sy2">++</span> <span class="sy1">=</span> p<span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw4">void</span> reallocate<span class="br0">(</span>size_t sz<span class="br0">)</span> <span class="br0">{</span> ... <span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">void</span> ptr_vector_base<span class="sy4">::</span><span class="me2">push_back_fwd</span><span class="br0">(</span><span class="kw4">void</span><span class="sy2">*</span> p<span class="br0">)</span> <span class="br0">{</span> pImpl<span class="sy2">-</span><span class="sy1">></span>push_back<span class="br0">(</span>p<span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
ptr_vector_base<span class="sy4">::</span><span class="me2">ptr_vector_base</span><span class="br0">(</span><span class="br0">)</span> <span class="sy4">:</span> pImpl<span class="br0">(</span><a href="cpp-memory-unique_ptr-make_unique.html"><span class="kw735">std::<span class="me2">make_unique</span></span></a><span class="sy1"><</span>impl<span class="sy1">></span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span></pre></div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E8.BF.90.E8.A1.8C.E6.97.B6.E5.BC.80.E9.94.80">运行时开销</span></h4>
|
|
<ul>
|
|
<li>访问开销:pImpl 中,每次对私有成员函数的调用都通过一个指针间接进行。私有成员对公开成员的每次访问也都通过另一个指针间接进行。两个访问都跨翻译单元边界,从而只能被连接时优化优化掉。注意 OO 工厂对公开数据和实现细节的访问都要求跨翻译单元间接进行,而且由于虚派发而给予连接时优化器更少的机会。</li>
|
|
<li>空间开销:pImpl 添加一个指针到公开组分,并且若有任何私有成员需要访问公开成员,则要么添加另一个指针到实现组分,要么每次调用要求它的私有成员时作为参数传递。若支持有状态自定义分配器,则必须一同存储分配器实例。</li>
|
|
<li>生存期管理开销:pImpl(还有 OO 工厂)将实现对象放在堆上,这在构造与销毁时强加了显著的运行时开销。这可以部分地由自定义分配器所弥补,因为 pImpl(但非 OO 工厂)的大小在编译时是已知的。</li>
|
|
</ul>
|
|
<p>另一方面,pImpl 类是对移动友好的;把大型的类重构为可移动 pImpl,可以提升对保有这些对象的容器进行操作的算法性能,即便可移动 pImpl 也具有额外的运行时开销:任何在被移动对象上容许使用并需要访问私有实现的公开成员函数必须进行空指针检查。</p>
|
|
<table class="metadata plainlinks ambox mbox-small-left ambox-notice" style="">
|
|
<tr>
|
|
<td class="mbox-empty-cell"></td>
|
|
<td class="mbox-text" style="">本节未完成<br />
|
|
原因:微基准测试?)</td>
|
|
</tr>
|
|
</table>
|
|
<h4><span class="mw-headline" id=".E7.BB.B4.E6.8A.A4.E5.BC.80.E9.94.80">维护开销</span></h4>
|
|
<p>pImpl 的使用要求专用的翻译单元(只有头文件的库无法使用 pImpl),引入一个额外类,一组转发函数,且当使用分配器时,会暴露公开接口所使用的分配器的细节。</p>
|
|
<p>因为虚成员是 pImpl 的接口组分的一部分,故模拟 pImpl 意味着单独模拟接口组分。可测试的 pImpl 典型地被设计为允许通过可用接口达成完整的测试覆盖。</p>
|
|
<h3><span class="mw-headline" id=".E5.AE.9E.E7.8E.B0">实现</span></h3>
|
|
<p>由于接口类型的对象控制实现类型对象的生存期,指向实现的指针通常是 <span class="t-lc"><a href="cpp-memory-unique_ptr.html">std::unique_ptr</a></span>。</p>
|
|
<p>因为 <span class="t-lc"><a href="cpp-memory-unique_ptr.html">std::unique_ptr</a></span> 要求被指向类型在任何实例化删除器的语境中均为完整类型,故特殊成员函数必须为用户声明,且在实现类为完整的实现文件中类外定义。</p>
|
|
<p>因为当 const 成员通过非 const 成员指针调用函数时会调用实现函数的非 const 重载,故该指针必须包装于 <span class="t-lc"><a href="cpp-experimental-propagate_const.html">std::experimental::propagate_const</a></span> 或等价物中。</p>
|
|
<p>将所有私有数据成员和所有私有非虚成员函数置于实现类中。将所有公开、受保护和虚成员留于接口类中(对替代方案的讨论见 <a rel="nofollow" class="external text" href="http://herbsutter.com/gotw/_100/">GOTW #100</a>)。</p>
|
|
<p>若任何私有成员需要访问公开或受保护成员,则可以将指向接口的引用或指针作为参数传递给私有函数。另外,也可以将回溯引用作为实现类的一部分维持。</p>
|
|
<p>若有意使用非默认分配器以支持实现对象的分配,则可以利用任何常用的具分配器模式,包括缺省为 <span class="t-lc"><a href="cpp-memory-allocator.html">std::allocator</a></span> 的分配器模板形参,以及类型为 <a href="cpp-memory-memory_resource.html"><tt>std::pmr::memory_resource*</tt></a> 的构造函数实参。</p>
|
|
<h3><span class="mw-headline" id=".E7.A4.BA.E4.BE.8B">示例</span></h3>
|
|
<div class="t-example">
|
|
<p>演示带有 const_propagation 的 pImpl,带有作为参数传递的回溯引用,不具分配器,并启用不带运行时检查的移动</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <iostream></span>
|
|
<span class="co2">#include <memory></span>
|
|
<span class="co2">#include <experimental/propagate_const></span>
|
|
|
|
<span class="co1">// 接口(widget.h)</span>
|
|
<span class="kw1">class</span> widget <span class="br0">{</span>
|
|
<span class="kw1">class</span> impl<span class="sy4">;</span>
|
|
<a href="cpp-experimental-propagate_const.html"><span class="kw2423">std::<span class="me2">experimental</span><span class="sy4">::</span><span class="me2">propagate_const</span></span></a><span class="sy1"><</span><a href="cpp-memory-unique_ptr.html"><span class="kw733">std::<span class="me2">unique_ptr</span></span></a><span class="sy1"><</span>impl<span class="sy1">>></span> pImpl<span class="sy4">;</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> draw<span class="br0">(</span><span class="br0">)</span> <span class="kw4">const</span><span class="sy4">;</span> <span class="co1">// 公开 API,将被转发给实现</span>
|
|
<span class="kw4">void</span> draw<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw4">bool</span> shown<span class="br0">(</span><span class="br0">)</span> <span class="kw4">const</span> <span class="br0">{</span> <span class="kw1">return</span> <span class="kw2">true</span><span class="sy4">;</span> <span class="br0">}</span> <span class="co1">// 公开 API,实现必须调用之</span>
|
|
widget<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">;</span>
|
|
~widget<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 定义于实现文件,其中 impl 是完整类型</span>
|
|
widget<span class="br0">(</span>widget<span class="sy3">&&</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 定义于实现文件 </span>
|
|
<span class="co1">// 注意:在被移动的对象上调用 draw() 是 UB</span>
|
|
widget<span class="br0">(</span><span class="kw4">const</span> widget<span class="sy3">&</span><span class="br0">)</span> <span class="sy1">=</span> delete<span class="sy4">;</span>
|
|
widget<span class="sy3">&</span> operator<span class="sy1">=</span><span class="br0">(</span>widget<span class="sy3">&&</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 定义于实现文件</span>
|
|
widget<span class="sy3">&</span> operator<span class="sy1">=</span><span class="br0">(</span><span class="kw4">const</span> widget<span class="sy3">&</span><span class="br0">)</span> <span class="sy1">=</span> delete<span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="co1">// 实现(widget.cpp)</span>
|
|
<span class="kw1">class</span> widget<span class="sy4">::</span><span class="me2">impl</span> <span class="br0">{</span>
|
|
<span class="kw4">int</span> n<span class="sy4">;</span> <span class="co1">// 私有数据</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> draw<span class="br0">(</span><span class="kw4">const</span> widget<span class="sy3">&</span> w<span class="br0">)</span> <span class="kw4">const</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span><span class="br0">(</span>w.<span class="me1">shown</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span> <span class="co1">// 对公开成员函数的此调用要求回溯引用</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"drawing a const widget "</span> <span class="sy1"><<</span> n <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw4">void</span> draw<span class="br0">(</span><span class="kw4">const</span> widget<span class="sy3">&</span> w<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">if</span><span class="br0">(</span>w.<span class="me1">shown</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"drawing a non-const widget "</span> <span class="sy1"><<</span> n <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
impl<span class="br0">(</span><span class="kw4">int</span> n<span class="br0">)</span> <span class="sy4">:</span> n<span class="br0">(</span>n<span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">void</span> widget<span class="sy4">::</span><span class="me2">draw</span><span class="br0">(</span><span class="br0">)</span> <span class="kw4">const</span> <span class="br0">{</span> pImpl<span class="sy2">-</span><span class="sy1">></span>draw<span class="br0">(</span><span class="sy2">*</span>this<span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="kw4">void</span> widget<span class="sy4">::</span><span class="me2">draw</span><span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> pImpl<span class="sy2">-</span><span class="sy1">></span>draw<span class="br0">(</span><span class="sy2">*</span>this<span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
widget<span class="sy4">::</span><span class="me2">widget</span><span class="br0">(</span><span class="kw4">int</span> n<span class="br0">)</span> <span class="sy4">:</span> pImpl<span class="br0">{</span><a href="cpp-memory-unique_ptr-make_unique.html"><span class="kw735">std::<span class="me2">make_unique</span></span></a><span class="sy1"><</span>impl<span class="sy1">></span><span class="br0">(</span>n<span class="br0">)</span><span class="br0">}</span> <span class="br0">{</span><span class="br0">}</span>
|
|
widget<span class="sy4">::</span><span class="me2">widget</span><span class="br0">(</span>widget<span class="sy3">&&</span><span class="br0">)</span> <span class="sy1">=</span> <span class="kw1">default</span><span class="sy4">;</span>
|
|
widget<span class="sy4">::</span>~widget<span class="br0">(</span><span class="br0">)</span> <span class="sy1">=</span> <span class="kw1">default</span><span class="sy4">;</span>
|
|
widget<span class="sy3">&</span> widget<span class="sy4">::</span><span class="me2">operator</span><span class="sy1">=</span><span class="br0">(</span>widget<span class="sy3">&&</span><span class="br0">)</span> <span class="sy1">=</span> <span class="kw1">default</span><span class="sy4">;</span>
|
|
|
|
<span class="co1">// 用户(main.cpp)</span>
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
widget w<span class="br0">(</span><span class="nu0">7</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw4">const</span> widget w2<span class="br0">(</span><span class="nu0">8</span><span class="br0">)</span><span class="sy4">;</span>
|
|
w.<span class="me1">draw</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
w2.<span class="me1">draw</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">drawing a non-const widget 7
|
|
drawing a const widget 8</pre></div>
|
|
</div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E6.B3.A8.E8.A7.A3">注解</span></h3>
|
|
<table class="metadata plainlinks ambox mbox-small-left ambox-notice" style="">
|
|
<tr>
|
|
<td class="mbox-empty-cell"></td>
|
|
<td class="mbox-text" style="">本节未完成<br />
|
|
原因:注意连接到值语义多态</td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E5.BC.95.E7.94.A8">引用</span></h3>
|
|
<ol class="references">
|
|
<li id="cite_note-1"><span class="mw-cite-backlink"><a href="cpp-language-pimpl.html#cite_ref-1">↑</a></span> <span class="reference-text"><a rel="nofollow" class="external text" href="http://herbsutter.com/gotw/_100/">GotW #100</a>:编译防火墙</span></li>
|
|
</ol>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|