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

Effective C++ 中文版 第三版》讀書筆記

** 條款 25:考慮寫出一個不拋異常的 swap 函數(shù) **

缺省情況下 swap 動作可由標準程序庫提供的 swap 算法完成:

namespace std{ 
    template<typename T> 
    void swap(T& a, T& b) 
    { 
        T temp(a); 
        a = b; 
        b = temp; 
    } 
}

但是對某些類型而言填大,這些復(fù)制動作無一必要:其中主要的就是“以指針指向一個對象伺通,內(nèi)含真正數(shù)據(jù)”那種類型骇笔。多為 “pimpl 手法”(pointer to implementation 的縮寫)祖今。設(shè)計 Widget class:

class WidgetImpl 
{ 
public: 
    ... 
protected: 
    ... 
private: 
    int a, b, c; 
    std::vector<double> v; 
    ... 
}; 

class Widget 
{ 
public: 
    Widget(const Widget& rhs); 
    Widget& operator=(const Widget& rhs) 
    { 
        ... 
        *pImpl = *(rhs.pImpl); 
    } 
protected: 

private: 
    WidgetImpl* pImpl; 
};

要置換兩個 Widget 對象值,唯一要做的就是置換 pImpl 指針更米,缺省的 swap 算法不知道這一點。不只復(fù)制 3 個 Widget 還復(fù)制 3 個 WidgetImpl 對象。非常缺乏效率!

我們可以將 std::swap 全特化:

namespace std{
    template<>
    void swap<Widget>(Widget& a, Widget& b)
    {
        swap(a.pImpl, b.pImpl); //< 不能通過編譯包雀,訪問 private 成員變量
    }
}

雖然可以把這個特化版本聲明為 friend,但這和以往的規(guī)矩不太一樣亲铡,我們可以令 Widget 聲明一個 swap 的 public 成員函數(shù)做真正的替換工作才写,然后將 std::swap 特化,令他調(diào)用該成員函數(shù):

class Widget 
{ 
public: 
    Widget(const Widget& rhs); 
    Widget& operator=(const Widget& rhs) 
    { 
        ... 
        *pImpl = *(rhs.pImpl); 
    } 

    void swap(Widget& other) 
    { 
        using std::swap; 
        swap(pImpl, other.pImpl); 
    } 

protected: 

private: 
    WidgetImpl* pImpl; 
};

namespace std{ 
    template<> 
    void swap<Widget>(Widget& a, Widget& b) 
    { 
        a.swap(b); 
    } 
}

假設(shè) Widget 和 WidgetImpl 都是 class templates 而非 classes:

template <typename T>
class WidgetImpl{…};

template <typename T>
class Widget{…};

在特化std::swap時:

namespace std{ 
    template<typename T> 
    void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) //wrong奴愉!不合法! 
    { 
        a.swap(b); 
    } 
}

我們企圖偏特化一個 function template铁孵,但是 c++ 中只允許對 class templates 偏特化锭硼,在 function template 身上偏特化是行不通的。這段代碼不該通過編譯(雖然有些編譯器錯誤地接受了它)蜕劝。

當(dāng)打算偏特化一個 function template 時檀头,慣常的做法是簡單的為它添加一個重載版本:

namespace std{ 
    template<typename T> 
    void swap(Widget<T>& a, Widget<T>& b) //注意“swap后沒有<>” 
    { 
        a.swap(b); 
    } 
}

一般而言,重載 function template 沒有問題岖沛,但 std 是個特殊的命名空間暑始,管理也就比較特殊∮は鳎客戶可以全特化 std 內(nèi)的 templates廊镜,但不可以添加新的 templates(或 class 或 function 或任何其他東西)到 std 里頭。其實跨越紅線的程序幾乎仍可編譯執(zhí)行唉俗,但他們行為沒有明確定義嗤朴。所以不要添加任何新東西到 std 里頭。

未提供較高效的 template 特定版本虫溜。我們還是聲明一個 non-member swap 但不再是 std::swap 的特化版本或重載版本:

``cpp
namespace WidgetStuff{
template<typename T>
class Widget{...};

template<typename T> 
void swap(Widget<T>& a, Widget<T>& b) 
{ 
    a.swap(b); 
} 

}


現(xiàn)在雹姊,任何地點的任何代碼如果打算置換兩個 Widget 對象,因而調(diào)用 swap衡楞,C++ 的名稱查找法則就是所謂 “argument-dependent lookup” 會找到 WidgetStuff 內(nèi)的專屬版本吱雏。

```cpp
template<typename T> 
void doSomething(T& obj1, T& obj2) 
{ 
    ... 
    swap(obj1, obj2); 
    ... 
}

上面的應(yīng)該使用哪個 swap?是 std 既有的那個一般化版本還是某個可能存在的特化版本等?你希望應(yīng)該是調(diào)用 T 專屬版本歧杏,并在該版本不存在的情況下調(diào)用 std 內(nèi)的一般化版本镰惦,下面是你希望發(fā)生的事:

template<typename T> 
void doSomething(T& obj1, T& obj2) 
{ 
    using std::swap; //< 令 std::swap 在此函數(shù)內(nèi)可用 
    ... 
    swap(obj1, obj2); //< 為 T 型對象調(diào)用最佳 swap 版本 
    ... 
}

c++ 的名稱查找法則(name lookup rules)確保將找到 global 作用域或 T 所在之命名空間內(nèi)的任何 T 專屬的 swap。如果 T 是 Widget 并且位于命名空間 WidgetStuff 內(nèi)得滤,編譯器會找出 WidgetStuff 內(nèi)的 swap陨献。如果沒有 T 專屬的 swap 存在,編譯器就是用 std 內(nèi)的 swap懂更,然而即便如此眨业,編譯器還是比較喜歡 std::swap 的 T 專屬特化版本,而非一般化的那個 template沮协。

std::swap(obj1,obj2); //< 這是錯誤的 swap 調(diào)用方式

這便強迫編譯器只認 std 內(nèi)的 swap(包括其任何 template 特化)龄捡,因此不再調(diào)用一個定義于他處的較適當(dāng) T 專屬版本。那正是“你的 classes 對 std::swap 進行全特化的”重要原因:使得類型專屬的 swap 實現(xiàn)版本可以被這些迷途代碼所用慷暂。

如果 swap 的缺省實現(xiàn)碼對你的 classes 或 class template 提供可接受的效率聘殖,不需要額外做任何事。

如果 swap 缺省實現(xiàn)版效率不足(某種 pimpl):

  1. 提供一個 public swap 成員函數(shù)行瑞,這個函數(shù)絕不該拋出異常奸腺。
  2. 在 class 或 template 所在的命名空間內(nèi)提供一個 non-member swap, 并令他調(diào)用上述 swap 成員函數(shù)血久。
  3. 如果正編寫一個 class(而非 class template)突照,為你的 class 特化 std::swap。并令他調(diào)用 swap 成員函數(shù)氧吐。

如果調(diào)用 swap讹蘑,確保包含一個 using 聲明式,然后不加任何 namespace 修飾符筑舅,赤裸裸調(diào)用 swap座慰。

成員版 swap 絕不可拋出異常。

唯一還未明確的勸告:成員版本的 swap 決不可拋出異常翠拣。那是因為 swap 的一個最好的應(yīng)用是幫助 classes(class templates)提供強烈的異常安全性保障版仔。(條款 29 對此提供了所有細節(jié))此技術(shù)基于一個假設(shè):成員版的 swap 絕不拋出異常。這一約束只施行于成員版误墓!不可施行于非成員版邦尊,因為 swap 缺省版本是以 copy 構(gòu)造函數(shù)和 copy assignment 操作符為基礎(chǔ),而一般情況下兩者都允許拋出異常优烧。因此當(dāng)你寫一個自定版本的 swap蝉揍,往往提供的不只是高效置換對象值的辦法,而且不拋出異常畦娄。一般又沾,這兩個 swap 特性是連在一起的弊仪,因為高效的 swaps 幾乎總是基于對內(nèi)置類型的操作(例如 pimpl 手法的底層指針),而內(nèi)置類型上的操作絕不會拋出異常杖刷。

** 請記桌: **

  1. 當(dāng) std::swap 對你的類型效率不高時,提供一個 swap 成員函數(shù)滑燃,并確定這個函數(shù)不拋出異常役听。
  2. 如果你提供一個 member swap,也該提供一個 non-member swap 用來調(diào)用前者表窘。對于 class(而非 template)典予,也請?zhí)鼗?std::swap。
  3. 調(diào)用 swap 時應(yīng)針對 std::swap 使用 using 聲明式乐严,然后調(diào)用 swap 并且不帶任何“命名空間資格修飾符”瘤袖。
  4. 為“用戶定義類型”進行 std template 全特化是好的,但千萬不要嘗試在 std 內(nèi)加入某些對 std 而言全新的東西昂验。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捂敌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子既琴,更是在濱河造成了極大的恐慌占婉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甫恩,死亡現(xiàn)場離奇詭異逆济,居然都是意外死亡,警方通過查閱死者的電腦和手機填物,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門纹腌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霎终,“玉大人滞磺,你說我怎么就攤上這事±嘲” “怎么了击困?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長广凸。 經(jīng)常有香客問我阅茶,道長,這世上最難降的妖魔是什么谅海? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任脸哀,我火速辦了婚禮,結(jié)果婚禮上扭吁,老公的妹妹穿的比我還像新娘撞蜂。我一直安慰自己盲镶,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布蝌诡。 她就那樣靜靜地躺著溉贿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浦旱。 梳的紋絲不亂的頭發(fā)上宇色,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音颁湖,去河邊找鬼宣蠕。 笑死,一個胖子當(dāng)著我的面吹牛爷狈,可吹牛的內(nèi)容都是我干的植影。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涎永,長吁一口氣:“原來是場噩夢啊……” “哼思币!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羡微,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谷饿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妈倔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體博投,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年盯蝴,在試婚紗的時候發(fā)現(xiàn)自己被綠了毅哗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡捧挺,死狀恐怖虑绵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闽烙,我是刑警寧澤翅睛,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站黑竞,受9級特大地震影響捕发,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜很魂,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一扎酷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遏匆,春花似錦法挨、人聲如沸骤铃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惰爬。三九已至,卻和暖如春惫企,著一層夾襖步出監(jiān)牢的瞬間撕瞧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工狞尔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丛版,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓偏序,卻偏偏與公主長得像页畦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子研儒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容