swap是STL中的标准函数,用于交换两个对象的数值。后来swap成为异常安全编程(exception-safe programming,条款29)的脊柱,也是实现自我赋值(条款11)的一个常见机制。swap的实现如下:
<code class="language-c++ hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">namespace</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap(T& a, T& b) { T temp(a); a=b; b=temp; } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
只要T支持copying函数(copy构造函数和copy assignment操作符)就能允许swap函数。这个版本的实现非常简单,a复制到temp,b复制到a,最后temp复制到b。
但是对于某些类型而言,这些复制可能无一必要。例如,class中含有指针,指针指向真正的数据。这种设计常见的表现形式是所谓的“pimpl手法“(pointer to implementation,条款31)。如果以这种手法设计Widget class
<code class="language-C++ hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> WidgetImpl{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span>: …… <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span>: <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> a,b,c; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//数据很多,复制意味时间很长</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>::<span class="hljs-stl_container" style="box-sizing: border-box;"><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">vector</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">double</span>></span> b; …… };</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
下面是pimpl实现
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">class Widget{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span>: <span class="hljs-title" style="box-sizing: border-box;">Widget</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> Widget& rhs); Widget& <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">operator</span>=(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> Widget& rhs { …… <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//复制Widget时,复制WidgetImpl对象 </span> *pImpl=*(ths.pImpl); …… } …… <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span>: WidgetImpl* pImpl;<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//指针,含有Widget的数据</span> };</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>
如果置换两个Widget对象值,只需要置换其pImpl指针,但STL中的swap算法不知道这一点,它不只是复制三个Widgets,还复制WidgetImpl对象,非常低效。
我们希望告诉std::swap,当Widget被置换时,只需要置换其内部的pImpl指针即可,下面是基本构想,但这个形式无法编译(不能访问private)。
<code class="language-c++ hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">namespace <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">std</span>{ template<> //这是<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">std</span>::<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">swap</span>针对T是Widget的特换版本, void <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">swap</span><Widget>(Widget& a, Widget& b) //目前还无法编译 { //只需要置换指针 <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">swap</span>(a<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.pImpl</span>, b<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.pImpl</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">; </span> } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>
其中template<>
表示std::swap
的一个全特化(total template specialization),函数名之后的<Widget>
表示这一特化版本系针对T是Widget
而设计的。我们被允许改变std
命名空间的任何代码,但是可以为标准的template编写特化版本,使它专属于我们自己的class。
上面函数试图访问private数据,因此无法编译。我们可以将swap函数声明为friend,但这个和以往有点不同。可以令Widget的swap函数为public,然后将std::swap
特化
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">calss Widget{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span>: …… <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap(Widget& other) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">using</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>::swap;<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这个声明有必要</span> swap(pImpl, other.pImpl); } …… }; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">namespace</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//修订后的swap版本</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap<Widget>(Widget& a, Widget& b) { a.swap(b); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//调用其成员函数</span> } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li></ul>
这个做法还跟STL容器保持一致,因为STL容器也提供public swap
和特化的std::swap
(用来条用前者)。
刚刚假设Widget和WidgetImpl都是class,而不是class template,如果是template时:
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> WidgetImpl{……}; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> Widget{……};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
可以在Widget内或WidgetImpl内放个swap成员函数,像上面一样。但是在特化std:swap
时会遇到麻烦
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">namespace</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap<Widget<T> >(Widget<T>& a,<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//不合法,错误</span> Widget<T>& b) { a.swap(b); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
看起来合理却不合法。上面是企图偏特化(partially specialize)一个function template(std::swap),但C++只允许对class template偏特化,在function templates身上偏特化行不通,这段代码不该通过编译。
当偏特化一个function template时,通常简单地为它添加一个重载版本
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">namespace</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>{ <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//std::swap一个重载版本</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap(Widget<T>& a,<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//swap后面没有<……></span> Widget<T>& b)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这个也不合法</span> { a.swap(b); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
一般而言,重载function template没有任何问题,但std是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的templates,但是不可以添加新的classes或functions到std里面。std的内容有c++标准委员会决定,标准委员会禁止我们膨胀那些已经 声明好的东西。
正确的做法是声明一个non-member swap 让他来调用member swap,但不再将那个non-member swap声明为std::swap。把Widget相关机能都置于命名空间WidgetStuff
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">namespace</span> WidgetStuff{ ……<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//模板化的WidgetImpl等</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//内含swap函数</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> Widget{……}; …… <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> swap(Widget<T>& a,<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//non-member,不属于std命名空间</span> Widget<T>& b) { a.swap(b); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul>
上面的做法对于class和class template都适用,但是如果你想让你的“class专属版”swap在尽可能多的语境下被调用,你需要同时在该class所在命名空间内写一个non-member版本以及一个std::swap版本。
现在所做的都与swap相关,换位思考一下,从客户角度来看,假设你正在写一个function template,其内需要置换两个对象的值
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">template<typename <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">T</span>> void doSomething(<span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">T</span>& obj1, <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">T</span>& obj2) { …… swap(obj1, obj2); …… }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>
这时应该调用哪个swap?是std既有的,还是某个可能存在的特化版本,再或则是可能存在一个可能存在的T专属版本且可能栖身于某个命名空间。我们希望首先调用T的专属版本,当该版本不存在的情况下调用std的一般户版本。
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">template</span><<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typename</span> T> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> doSomething(T& obj1, T& obj2) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">using</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">std</span>::swap;<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//令std::swap在此函数内可用</span> …… swap(obj1, obj2);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//位T类型调用最佳版本swap</span> …… }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
当编译器看到对swap调用时,便去找最合适的。C++的名称查找法则(name lookup rules)确保找到global作用域或T所在命名空间内的任何T专属的swap。如果T是Widget并在命名空间WidgetStuff内,编译器或使用“实参取决之查找规则”(argument-dependent lookup)找到WidgetStuff内的swap,如果没有专属版的swap,那么会调用std内的swap(因为使用了using std::swap
)。
现在已经讨论了dufault swap、member swap、non-member swaps、std::swap特化版本、以及对swap的调用,下面做个总结。
首先,如果如果swap的缺省实现对我们的class或class template效率可以接受,那么无需做任何事。
其次,如果swap缺省实现版 的效率不足(例如,你的class或template使用了某种pimple手法),试着做以下事情:
1、提供一个public swap成员函数,让它高效置换两个对象值。这个函数不应该抛出异常。
2、在你的class或template所在命名空间提供一个non-member swap,并令它调用上述swap成员函数。
3、如果你编写的是class(不是class template),为你的class 特化std::swap,并令它调用你的swap成员函数。
如果调用swap,那么要使用using声明式,确保让std::swap在你的函数内可见。
成员版的swap函数决不能抛出异常,因为swap的一个最好应用是帮助class或class template提供强烈的异常安全性(exception-safety)保障。条款29对此提供细节,但此技术基于一个假设:成员版swap绝不抛出异常。这个约束只施行在成员版,不用于非成员版。因为std::swap是以copy函数为基础,而copy函数允许抛出异常。
当我们编写自定版的swap时,不是仅仅提供高效的置换对象值的方法,还要不抛出异常。这两个特性总是连在一起,因为高效的swap几乎总是基于对内置类型(例如pimple手法的底层指针),而内置类型上操作不允许抛出异常。
总结
1、如果std::swap不高效时,提供一个swap成员函数,并且确定这个函数不抛出异常。
2、如果提供一个member-swap,也应该提供一个non-member swap来调用前者。对于class(非class template),要特化std::swap。
3、调用swap时,针对std::swap使用using形式,然后调用swap并且不带任何命名空间资格修饰。
4、为“用户定义类型”进行std template全特化时,不要试图在std内加入某些对std而言是全新的东西