《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):
- 提供一個 public swap 成員函數(shù)行瑞,這個函數(shù)絕不該拋出異常奸腺。
- 在 class 或 template 所在的命名空間內(nèi)提供一個 non-member swap, 并令他調(diào)用上述 swap 成員函數(shù)血久。
- 如果正編寫一個 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)置類型上的操作絕不會拋出異常杖刷。
** 請記桌: **
- 當(dāng) std::swap 對你的類型效率不高時,提供一個 swap 成員函數(shù)滑燃,并確定這個函數(shù)不拋出異常役听。
- 如果你提供一個 member swap,也該提供一個 non-member swap 用來調(diào)用前者表窘。對于 class(而非 template)典予,也請?zhí)鼗?std::swap。
- 調(diào)用 swap 時應(yīng)針對 std::swap 使用 using 聲明式乐严,然后調(diào)用 swap 并且不帶任何“命名空間資格修飾符”瘤袖。
- 為“用戶定義類型”進行 std template 全特化是好的,但千萬不要嘗試在 std 內(nèi)加入某些對 std 而言全新的東西昂验。