考慮寫出一個不拋出異常的swap函數

條款25:考慮寫出一個不拋出異常的swap函數

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而言是全新的東西

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章