考慮寫出乙個不丟擲異常的swap函式

2021-07-11 02:55:41 字數 4023 閱讀 2601

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...