考慮寫一個不拋異常的swap函式
swap函式存在於STL中,其典型實現如下:
namespace std
{
template <typename T>
void swap(T &a, T &b)
{
T Temp(a);
a = b;
b = Temp;
}
}
這個函式執行了物件a複製到temp,b複製到a,Temp複製到b,但是對於某些情況,多餘的複製是沒有必要的:
考慮下面的例子:
class WidgetImpl { public: ... private: int a, b, c; std::vector<double> v; }; class Widget { Widget(const Widget &rhs); Widget &operator =(const Widget &rhs); private: WdigetImpl *pImpl; };
一旦要交換兩個Widget物件的值,我們僅僅需要的就是置換其pImpl指標,但預設的演算法不止複製三個Widgets,還複製三個WidgetImpl物件,非常低效。
我們需要告訴預設的swap函式,只需要交換兩個指標就好,為了實現這個思路,考慮如下做法:
namespace std { template <> void swap<Widget>(Widget &a, Widget &b) { swap(a.pImpl, b.pImpl); } }
這個做法叫做函式的特化https://mp.csdn.net/postedit/83651232。
但是上述做法並不能通過編譯,因為pImpl是類的私有成員,我們無法在類外呼叫。
既然如此,那麼我們就讓特化的swap函式成為Widget類的類成員:
class Widget { public: ... Widget(const Widget &rhs); Widget &operator =(const Widget &rhs); public: void swap(Widget &Other) { using std::swap; swap(pImpl, Other.pImpl); } private: WdigetImpl *pImpl; }; namespace std { template <> void swap<Widget>(Widget &a, Widget &b) { a.swap(b); } }
這樣,就可以通過編譯。
上述做法是基於類都是class而不是template class,下面考慮一下如果類都是template class會發生什麼:
template <typename T>
class WidgetImpl{...};
template <typename T>
class Widget{...};
namespace std
{
template <typename T>
void swap<Widget<T>>(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
這種情況是不能通過編譯的,因為函式是不能夠被偏特化的,只有類才能偏特化。
一般情況下,偏特化一個函式的代替做法為過載函式:
namespace std
{
template <typename T>
void swap(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
但是這也是不合法的,因為名稱空間std內是禁止新的東西的,你可以全特化,但不可以過載。
新增一個新的名稱空間可以解決這種局面:
namespace WidgetStuff
{
template <typename T>
class WidgetImpl{...};
template <typename T>
class Widget{...}; //包括成員函式swap
template <typename T>
void swap(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}
}
這樣一來,c++的名稱查詢法則會找到WidgetStuff內的Widget專屬版本。
這個做法既適用於class也適用於template class,但你最好還是為swap函式在名稱空間std中實現一個特化的版本:
template <typename T>
void DoSomething(T &a, T &b)
{
...
using std::swap;
swap(a, b);
...
}
如果這樣呼叫swap函式,那麼如果編譯器找不到針對T在某個名稱空間內的專屬版本,就會呼叫std內的預設版本,但是如果這樣呼叫:
template <typename T>
void DoSomething(T &a, T &b)
{
...
std::swap(a, b);
...
}
那麼就會直接呼叫std中的預設版本, 所以為了使這種錯誤的呼叫方式也可以呼叫適當的swap函式,你可以在std中實現一個全特化的版本,這樣編譯器會首先呼叫全特化版本。
總結:
如果std版本的效率滿足你的要求,可以什麼都不做,否則:
1. 在類中提供一個public function高效的置換;
2. 在class或template class所在的名稱空間提供一個non-member函式去呼叫成員函式;
3. 如果你編寫的是class而不是template class,為你的class特化std::swap。
最後,注意函式的呼叫方式。
還有一點:成員版swap函式不應該丟擲異常。