Effective C 讀書筆記之七

2021-05-28 03:03:52 字數 3354 閱讀 8220

條款八:別讓異常逃離析構函式

c++並不禁止析構函式吐出異常,但它不鼓勵你這樣做。這是有理由的。這是有理由的。考慮以下**:

class widget

// 假設這個可能吐出乙個異常

} ;

void dosomething()

當vector被銷毀,它有責任銷毀其內含的widgets。假設v內含是個widgets,而在析構第乙個期間,有個異常被丟擲。其他九個widgets還是應該被銷毀(否則它們儲存的任何資源都會發生洩露),因此v因該呼叫它們各個析構函式。但假設在那些呼叫期間,第二個widget析構函式又丟擲異常。現在有兩個同時作用的異常,這對c++而言太多了。在兩個異常同時存在的情況下,程式若不是結束執行就是導致不明確行為。本例中它會導致不明確的行為。使用標準程式庫的任何其他容器(如list,set)或tr1的任何容器或甚至array,也會出現相同的情況。容器或array並非遇上麻煩的額必要條件,只要析構函式吐出異常,即使並非使用容器或arrays,程式也可能過早結束或出現不明確行為。是的,c++不喜歡析構函式吐出異常!

這很容易理解,但如果你的析構函式必須執行乙個動作,而該動作可能會在失敗時丟擲異常,該怎麼辦?舉個例子,假設你使用乙個class負責資料庫連線:

class dbconnection

為確保客戶不忘記在dbconnection物件身上呼叫close(),乙個合理的想法是建立乙個用來管理dbconnection資源的class,並在其析構函式中呼叫呼叫close。這一類用於資源管理的classes在後面的部落格中會討論,在這裡主要考慮析構函式的長相就足夠了:

class dbconn// 這個class用來管理dbconnection物件

private:

dbconnection db ;

}

這便允許客戶寫出這樣的**:

// 在區塊結束點,dbconn物件被銷毀,因而自動為dbconnection物件呼叫close

只要呼叫close成功,一切都美好。但如果該呼叫導致異常,dbconn析構函式會傳播該異常,也就是允許它離開這個析構函式。那會造成問題,因為那就是丟擲了難以駕馭的麻煩。

兩個辦法可以避免這一問題。dbconn的析構函式可以:

* dbconn::~dbconn()

catch(...)

}

如果程式遭遇乙個「於析構期間發生的錯誤」後無法繼續執行,「強迫計數程式」是個合理選項。畢竟它可以阻止異常從析構函式傳播出去(那會導致不明確行為)。也就是說呼叫abort可以搶先制「不明確行為」於死地。

* 吞下因呼叫close而發生的異常:

dbconn::~dbconn()

catch(...)

}

一般而言,將異常吞掉是個壞主意,因為它壓制了「某些動作失敗」的重要資訊!然而有時候吞下異常也比負擔「草率計數程式」或「不明確行為帶來的風險」好。為了讓著成為乙個可行方案,程式必須能夠繼續可靠地執行,即使在遭遇並忽略乙個錯誤之後。

這些辦法都沒什麼吸引力。問題在於兩者都無法對「導致close丟擲異常」的情況作出反應。

乙個較佳策略師重新設計dbconn介面,使其客戶有機會對可能出現的問題作出反應。例如dbconn自己可以提供乙個close函式,因而賦予客戶乙個機會得以處理「因該操作而發生的異常」。dbconn也可以追蹤其所管理之dbconnection是否已被關閉,並在答案為否的情況下由其析構函式關閉之。這可防止資料庫連線。然而如果dbconnection析構函式呼叫close失敗,我們將退回「強迫結束程式」或「吞下異常」的老路:

class dbconn

~dbconn()

catch(...)

}

private:

dbconnection db ;

bool closed ;

} ;

把呼叫close的責任從dbconn析構函式手上轉移到dbconn客戶手上(但dbconn析構函式仍內含乙個「雙保險」呼叫)可能會給你「肆無忌憚轉移負擔」的印象。你甚至可能認為它違反條款18所提忠告(讓介面容易被正確使用)。實際上這兩項汙名都不成立。如果某個操作可能在失敗時丟擲異常,而又存在某種需要必須處理該異常,那麼這個異常必須來自析構函式以外的某個函式。因為析構函式吐出異常就是危險,總會帶來「過早結束程式」或「發生不明確行為」的風險。本例要說的是,由客戶自己呼叫close並不會對他們帶來負擔,而是給他們乙個處理錯誤的機會,否則它們沒機會響應。如果它們不認為這個機會有用(或許它們堅信不會有錯誤發生),可以忽略它,依賴dbconn析構函式去呼叫close。如果真有錯誤發生---如果close的確丟擲異常---而且dbconn吞下該異常或結束程式,客戶沒有立場抱怨,畢竟它們曾經有機會第一手處理問題,而它們選擇了放棄。

請記住:

* 析構函式絕對不要吐出異常。如果乙個析構函式呼叫的函式可能丟擲異常,析構函式應該捕獲任何異常,然後吞下它們(不傳播)或結束程式。

* 如果客戶需要對某個操作函式執行期間丟擲的異常作出反應,那麼class應該提供乙個普通函式(而非在析構函式)執行該操作。

《effective C 》讀書筆記

1,c 關鍵字explicit c 中,乙個引數的 建構函式 或者除了第乙個引數外其餘引數都有預設值的多參建構函式 承擔了兩個角色。1 是個 構造器,2 是個預設且隱含的型別轉換操作符 所以,有時候在我們寫下如 aaa 這樣的 且恰好 的型別正好是aaa單引數構造器的引數型別,這時候 編譯器就自動呼...

Effective C 讀書筆記

一 讓自己習慣c 1 條款01 視c 為聯邦語言 c 的組成可分為四部分 1.c c 仍然以c語言為基礎。區塊 語句 預處理 內建資料型別 陣列 指標等都來自c。2.object oriented c c with classes所訴說的 classes 包括構造和析構 封裝 繼承 多型 virtu...

讀書筆記 Effective C

部分條款過於深奧,部分條款已了然於心,僅記錄當下所識所學 對於常量巨集定義,最好用const代替 define 對於函式巨集定義,最好用inline代替 define include ifdef ifndef仍被需要 內建物件記得手動初始化 使用成員初始列替換賦值操作 以local static替換...