Effective C 讀書筆記(15)

2021-06-03 14:09:06 字數 3765 閱讀 7830

條款25:考慮寫出乙個不拋異常的swap函式

consider support for a non-throwing swap

swap是乙個有趣的函式。最早作為stl的一部分被引入,後來它成為異常安全程式設計(exception-safeprogramming)的支柱,和用來處理自我賦值可能性的常見機制。因為 swap太有用了,所以正確地實現它非常重要,但是伴隨它不同尋常的重要性而來的,是一系列不同尋常的複雜性。

swap兩個物件的值就是互相把自己的值賦予對方。預設情況下,swap動作可由標準程式庫提供的swap演算法完成,其典型的實現完全符合你的預期:

namespace std

}只要你的型別支援拷貝(通過拷貝建構函式和拷貝賦值運算子),預設的swap實現就能交換型別為t的物件,而不需要你做任何特別的支援工作。它涉及三個物件的拷貝:從a到temp,從 b到a,以及從temp到b。對一些型別來說,這些賦值動作全是不必要的。

這樣的型別中最重要的就是那些由乙個指標組成,這個指標指向包含真正資料的型別。這種設計方法的一種常見的表現形式是"pimpl手法"("pointerto implementation")。如果以這種手法設計widget 類,可能就像這樣:

class widgetimpl ;

class widget

...private:

widgetimpl *pimpl; // 指標,所指物件內含widget資料

}; 為了交換這兩個widget物件的值,我們實際要做的就是交換它們的pimpl指標,但是預設的交換演算法不僅要拷貝三個widgets,而且還有三個widgetimpl物件,效率太低了。當交換 widgets的是時候,我們應該告訴std::swap我們打算執行交換的方法就是交換它們內部的 pimpl指標。這種方法的正規說法是:針對widget特化std::swap。

class widget

...};

namespace std

}這個函式開頭的"template<>"表明它是std::swap的乙個全特化版本,函式名後面的""表明這一特化版本針對「t是widget」 而設計。換句話說,當通用的swap模板用於widgets時,便會啟用這個版本。通常,我們改變std namespace中的內容是不被允許的,但允許為為標準模板(如swap)製造特化版本,使它專屬於我們自己的類(如widget)。

我們在widget內宣告乙個名為swap的public成員函式去做真正的置換工作,然後特化 std::swap去呼叫那個成員函式。這樣不僅能夠編譯,而且和stl容器保持一致,所有stl容器都既提供了public swap成員函式,又提供了std::swap的特化來呼叫這些成員函式。

可是,假設widget和widgetimpl是類模板而不是類,或許我們可以試圖將widgetimpl中的資料型別加以引數化:

template

class widgetimpl ;

template

class widget ;

以下是方案1:

namespace std

}  //錯誤,不合法!

儘管c++允許類模板的偏特化(partialspecialization),但不允許函式模板這樣做。

以下是方案2:

namespace std

}  //這也不合法

通常,過載函式模板沒有問題,但是std是乙個特殊的命名空間,其規則也比較特殊。它認可完全特化std中的模板,但它不認可在std中增加新的模板(或類,函式,以及其它任何東西)。

正確的方法,既使其他人能呼叫swap,又能讓我們得到更高效的模板特化版本。我們還是宣告乙個非成員swap來呼叫成員swap,只是不再將那個非成員函式宣告為std::swap的特化或過載。例如,如果widget相關機能都在namespace widgetstuff中:

namespace widgetstuff ;

...template// non-member swap函式,這裡並不屬於std命名空間

voidswap(widget& a, widget& b)

}現在,如果某處有**打算置換兩個widget物件,呼叫了swap,c++的名字查詢規則將找到widgetstuff中的widget專用版本。

現在從客戶的觀點來看一看,假設你寫了乙個函式模板來交換兩個物件的值,哪乙個swap應該被呼叫呢?std中的通用版本,還是std中通用版本的特化,還是t專用版本(肯定不在std中)?如果t專用版本存在,則呼叫它;否則就回過頭來呼叫std中的通用版本。如下這樣就可以符合你的希望:

template

void dosomething(t& obj1, t& obj2)

當編譯器看到這個swap呼叫,他會尋找正確的swap版本來呼叫。如果t是namespacewidgetstuff中的widget,編譯器會利用引數依賴查詢(argument-dependent lookup)找到widgetstuff中的swap;如果t專用swap不存在,編譯器將使用std中的swap,這歸功於此函式中的using宣告式使std::swap在此可見。儘管如此,相對於通用模板,編譯器還是更喜歡t專用的std::swap特化,所以如果std::swap對t進行了特化,則特化的版本會被使用。

需要小心的是,不要對呼叫加以限定,因為這將影響c++挑選適當函式:

std::swap(obj1, obj2); // the wrong way to callswap

這將強制編譯器只考慮std中的swap(包括任何模板特化),因此排除了定義在別處的更為適用的t專用版本被呼叫的可能性。

總結:

首先,如果swap的預設實現為你的類或類模板提供了可接受的效能,你不需要做任何事。任何試圖交換型別的物件的操作都會得到預設版本的支援,而且能工作得很好。

第二,如果swap預設實現效率不足(這幾乎總是意味著你的類或模板使用了某種pimpl手法),就按照以下步驟來做:

1.   提供乙個public的swap成員函式,能高效地交換你的型別的兩個物件值,這個函式應該永遠不會丟擲異常。

2.   在你的類或模板所在的同乙個namespace中,提供乙個非成員的swap,用它呼叫你的swap成員函式。

3.   如果你寫了乙個類(不是類模板),為你的類特化std::swap,並令它呼叫你的swap 成員函式。

最後,如果你呼叫swap,確保在你的函式中包含乙個using 宣告式使std::swap可見,然後在呼叫swap時不使用任何namespace修飾符。

警告: 絕不要讓swap的成員版本丟擲異常。這是因為swap非常重要的應用之一是為類(以及類模板)提供強大的異常安全(exception-safety)保證。如果你寫了乙個swap的自定義版本,那麼,典型情況下你提供乙個更有效率的交換值的方法,也保證這個方法不會丟擲異常。這兩種swap的特型緊密地結合在一起,因為高效的交換幾乎總是基於內建型別(如pimpl手法下的指標)的操作,而對內建型別的操作絕不會丟擲異常。

·如果 std::swap 對於你的型別來說是低效的,請提供乙個 swap 成員函式,並確保你的 swap 不會丟擲異常。

·如果你提供乙個成員 swap,請同時提供乙個呼叫成員swap的非成員swap。對於類(非模板),還要特化 std::swap。

·呼叫swap時,請為std::swap使用乙個using宣告式,然後在呼叫 swap時不使用任何namespace修飾符。

·為使用者定義型別全特化 std 模板是好的,但絕不要試圖往std中加入任何全新的東西。

Effective C 讀書筆記1

條款1 視c 為乙個語言聯邦 今天的c 已經是個多重范型程式語言,乙個支援過程形式 物件導向形式 函式形式 泛型形式 元程式設計形式的語言。為了理解c 必須認識其主要的次語言,總共4個 1.c2.object oriented c 3.template c 4.stl 條款2 盡量以const,en...

Effective C 讀書筆記 1

1.定義式是編譯器對此物件撥發記憶體的地點。2.explicit宣告的建構函式可被用於禁止編譯器執行非預期的型別轉換。注 對於單個引數的建構函式定義了從該形參型別到該型別的乙個 轉換。explicit只對建構函式起作用,用來抑制隱式轉換。class a int function a a 當呼叫 fu...

Effective C 讀書筆記1

tmp 模板元程式設計 0.explicit建構函式比non explicit建構函式好。1.可以用const 來代替 define 定義乙個常量。define沒有作用域,也沒有封裝性。class a const int a num num的定義。在宣告式中已經獲初值,所以無需在定義式給初值。當在類...