swap是stl中的標準函式,用於交換兩個物件的數值。後來swap成為異常安全程式設計(exception-safe programming,條款29)的脊柱,也是實現自我賦值(條款11)的乙個常見機制。swap的實現如下:
namespace
std}
只要t支援copying函式(copy建構函式和copy assignment操作符)就能允許swap函式。這個版本的實現非常簡單,a複製到temp,b複製到a,最後temp複製到b。
但是對於某些型別而言,這些複製可能無一必要。例如,class中含有指標,指標指向真正的資料。這種設計常見的表現形式是所謂的「pimpl手法「(pointer to implementation,條款31)。如果以這種手法設計widget class
class widgetimpl;
下面是pimpl實現
class widget
……private:
widgetimpl* pimpl;//指標,含有widget的資料
};
如果置換兩個widget物件值,只需要置換其pimpl指標,但stl中的swap演算法不知道這一點,它不只是複製三個widgets,還複製widgetimpl物件,非常低效。
我們希望告訴std::swap,當widget被置換時,只需要置換其內部的pimpl指標即可,下面是基本構想,但這個形式無法編譯(不能訪問private)。
namespace std
}
其中template<>
表示std::swap
的乙個全特化(total template specialization),函式名之後的
表示這一特化版本系針對t是widget
而設計的。我們被允許改變std
命名空間的任何**,但是可以為標準的template編寫特化版本,使它專屬於我們自己的class。
上面函式試圖訪問private資料,因此無法編譯。我們可以將swap函式宣告為friend,但這個和以往有點不同。可以令widget的swap函式為public,然後將std::swap
特化
calss widget
……};namespace
std}
這個做法還跟stl容器保持一致,因為stl容器也提供public swap
和特化的std::swap
(用來條用前者)。
剛剛假設widget和widgetimpl都是class,而不是class template,如果是template時:
template
class widgetimpl;
template
class widget;
可以在widget內或widgetimpl內放個swap成員函式,像上面一樣。但是在特化std:swap
時會遇到麻煩
namespace
std}
看起來合理卻不合法。上面是企圖偏特化(partially specialize)乙個function template(std::swap),但c++只允許對class template偏特化,在function templates身上偏特化行不通,這段**不該通過編譯。
當偏特化乙個function template時,通常簡單地為它新增乙個過載版本
namespace
std}
一般而言,過載function template沒有任何問題,但std是個特殊的命名空間,其管理規則也比較特殊。客戶可以全特化std內的templates,但是不可以新增新的classes或functions到std裡面。std的內容有c++標準委員會決定,標準委員會禁止我們膨脹那些已經 宣告好的東西。
正確的做法是宣告乙個non-member swap 讓他來呼叫member swap,但不再將那個non-member swap宣告為std::swap。把widget相關機能都置於命名空間widgetstuff
namespace widgetstuff;
……template
void swap(widget& a,//non-member,不屬於std命名空間
widget& b)
}
上面的做法對於class和class template都適用,但是如果你想讓你的「class專屬版」swap在盡可能多的語境下被呼叫,你需要同時在該class所在命名空間內寫乙個non-member版本以及乙個std::swap版本。
現在所做的都與swap相關,換位思考一下,從客戶角度來看,假設你正在寫乙個function template,其內需要置換兩個物件的值
templatet>
void dosomething(t& obj1, t& obj2)
這時應該呼叫哪個swap?是std既有的,還是某個可能存在的特化版本,再或則是可能存在乙個可能存在的t專屬版本且可能棲身於某個命名空間。我們希望首先呼叫t的專屬版本,當該版本不存在的情況下呼叫std的一般戶版本。
template
void dosomething(t& obj1, t& obj2)
當編譯器看到對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而言是全新的東西
考慮寫出乙個不丟擲異常的swap函式
1 當std swap對你的型別效率不高時,提供乙個swap成員函式,並確定這個函式不丟擲異常。2 如果你提供乙個member swap函式,也該提供乙個non member swap函式,後者用來呼叫前者。對於class 而非template 也請特化std swap 3 呼叫swap時應針對st...
條款25 考慮寫出乙個不丟擲異常的swap函式
swap原本只是stl的一部分,後面成為異常安全程式設計的脊柱,及處理自我賦值安全性的乙個常見機制。例子 標準程式庫提供的swap演算法 namespace s td 要求 型別t支援copying 通過copying建構函式和copyassignment操作符完成 上述 主要涉及三個物件的複製,但...
條款25 考慮寫出乙個不丟擲異常的swap函式
首先本篇部落格的主要思想是 系統自帶的swap函式有時候不能滿足我們的需求,所以在一些情況下我們就需要自己去寫swap函式。此條款的主要內容就是告訴你該如何去寫你要的swap函式,下面開始正文來好好地介紹一下此條款的內容 1.首先來看一下庫裡面給的swap函式的原型 可以看出來標準程式庫提供的swa...