《Effective C++》第三版中條款08建議不要在析構(gòu)函數(shù)中拋出異常,原因是C++異常機(jī)制不能同時(shí)處理兩個(gè)或兩個(gè)以上的異常。多個(gè)異常同時(shí)存在的情況下,程序若不結(jié)束,會(huì)導(dǎo)致不明確行為。如下代碼:
class Widget{public: ~Widget(){...} //假設(shè)這個(gè)可能吐出一個(gè)異常};void dosomething(){ vector<Widget> v;} //v在這里被自動(dòng)銷(xiāo)毀函數(shù)dosomething運(yùn)行結(jié)束后,最為棧對(duì)象的vector v將被銷(xiāo)毀,它同時(shí)也有責(zé)任銷(xiāo)毀其內(nèi)含的所有Widgets。假設(shè)v內(nèi)含十個(gè)Widgets,而在析構(gòu)第一個(gè)元素期間,有個(gè)異常被拋出。其他九個(gè)widgets還是應(yīng)該被銷(xiāo)毀(否則他們保存的任何資源都會(huì)發(fā)生泄漏),因此v應(yīng)該調(diào)用它們各個(gè)析構(gòu)函數(shù)。但假設(shè)在那些調(diào)用期間,第二個(gè)widget析構(gòu)函數(shù)又拋出異常,這就出現(xiàn)了上面說(shuō)的情況,多個(gè)異常同時(shí)存在的情況下,程序若不結(jié)束,會(huì)導(dǎo)致不明確行為。
如果析構(gòu)函數(shù)必須執(zhí)行一個(gè)動(dòng)作,而該動(dòng)作可能會(huì)在失敗時(shí)拋出異常,該怎么辦?舉個(gè)例子,假設(shè)你使用一個(gè)class負(fù)責(zé)數(shù)據(jù)庫(kù)連接:
class DBConnection { public: ... static DBConnection create(); //返回DBConnection對(duì)象;為求簡(jiǎn)化暫略參數(shù) void close(); //關(guān)閉聯(lián)機(jī);失敗則拋出異常。};為確保客戶不忘記在DBConnection對(duì)象身上調(diào)用close(),一個(gè)合理的想法是創(chuàng)建一個(gè)用來(lái)管理DBConection資源的class,并在其析構(gòu)函數(shù)中調(diào)用close。這就是著名的以以對(duì)象管理資源。
class DBConn { //這個(gè)class用來(lái)管理DBConnection對(duì)象 public: ... DBConn(const DBConnection& db){ this->db=db; } ~DBConn() //確保數(shù)據(jù)庫(kù)連接總是會(huì)被關(guān)閉 { db.close(); } PRivate: DBConnection db;};調(diào)用close成功,一切都美好。但如果該調(diào)用導(dǎo)致異常,DBConn析構(gòu)函數(shù)會(huì)傳播該異常,也就是允許它離開(kāi)這個(gè)析構(gòu)函數(shù)。那會(huì)造成問(wèn)題,解決辦法如下: (1)方法一:結(jié)束程序 如果close拋出異常就結(jié)束程序,通常調(diào)用abort完成:
DBConn::~DBconn(){ try { db.close(); } catch(...){ abort(); }}如果程序遭遇一個(gè)“于析構(gòu)期間發(fā)生的錯(cuò)誤”后無(wú)法繼續(xù)執(zhí)行,“強(qiáng)制結(jié)束程序”是個(gè)合理選項(xiàng),畢竟它可以阻止異常從析構(gòu)函數(shù)傳播出去(那會(huì)導(dǎo)致不明確的行為)。也就是說(shuō)調(diào)用abort可以搶先制“不明確行為”于死地。
(2)方法二:吞下因調(diào)用close而發(fā)生的異常
DBConn::~DBConn{ try{ db.close(); } catch(...) { //制作運(yùn)轉(zhuǎn)記錄,記下對(duì)close的調(diào)用失敗! }}一般而言,將異常吞掉是個(gè)壞主意,因?yàn)樗鼔褐屏恕澳承﹦?dòng)作失敗”的重要信息!然而有時(shí)候吞下異常也比負(fù)擔(dān)“草率結(jié)束程序”或“不明確行為帶來(lái)的風(fēng)險(xiǎn)”好。為了讓這成為一個(gè)可行方案,程序必須能夠繼續(xù)可靠的執(zhí)行。
(3)方法三:重新設(shè)計(jì)DBConn接口,使其客戶有機(jī)會(huì)對(duì)可能出現(xiàn)的異常作出反應(yīng) 我們可以給DBConn添加一個(gè)close函數(shù),賦予客戶一個(gè)機(jī)會(huì)可以處理“因該操作而發(fā)生的異常”。把調(diào)用close的責(zé)任從DBConn析構(gòu)函數(shù)手上移到DBConn客戶手中,你也許會(huì)認(rèn)為它違反了“讓接口容易被正確使用”的忠告。實(shí)際上這污名并不成立。如果某個(gè)操作可能在失敗的時(shí)候拋出異常,而又存在某種需要必須處理該異常,那么這個(gè)異常必須來(lái)自析構(gòu)函數(shù)以外的某個(gè)函數(shù)。因?yàn)槲鰳?gòu)函數(shù)吐出異常就是危險(xiǎn),總會(huì)帶來(lái)“過(guò)早結(jié)束程序”或“發(fā)生不明確行為”的風(fēng)險(xiǎn)。
class DBConn {public: ... void close() //供客戶使用的新函數(shù) { db.close(); closed = true; } ~DBConn(){ if(!closed) { try { //關(guān)閉連接(如果客戶不調(diào)用DBConn::close) db.close(); } catch(...) { //如果關(guān)閉動(dòng)作失敗,記錄下來(lái)并結(jié)束程序或吞下異常。 制作運(yùn)轉(zhuǎn)記錄,記下對(duì)close的調(diào)用失敗; ... } } }private: DBConnection db; bool closed;};本例要說(shuō)的是,由客戶自己調(diào)用close并不會(huì)對(duì)他們帶來(lái)負(fù)擔(dān),而是給他們一個(gè)處理錯(cuò)誤的機(jī)會(huì)。如果他們不認(rèn)為這個(gè)機(jī)會(huì)有用(或許他們堅(jiān)信不會(huì)有錯(cuò)誤發(fā)生),可能忽略它,依賴DBConn析構(gòu)函數(shù)去調(diào)用close。
請(qǐng)記住: (1)析構(gòu)函數(shù)絕對(duì)不要吐出異常,如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕捉任何異常,然后吞下它們(不傳播)或結(jié)束程序。 (2)如果客戶需要對(duì)某個(gè)操作函數(shù)運(yùn)行期間拋出的異常作出反應(yīng),那么class應(yīng)該提供一個(gè)普通函數(shù)(而非在析構(gòu)函數(shù)中)執(zhí)行該操作。
[1]Effective c++學(xué)習(xí)筆記——條款08:別讓異常逃離析構(gòu)函數(shù)
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注