[Effective C++ --029]为“异常安全”而努力是值得的
假設(shè)有個class用來表現(xiàn)夾帶背景圖案的GUI菜單單,這個class用于多線程環(huán)境,所以它有個互斥器(mutex)作為并發(fā)控制用:
1 class PrettyMenu{ 2 public: 3 ... 4 void changeBackground(std::istream& imgSrc); 5 ... 6 private: 7 Mutex mutex; 8 Image* bgImage; 9 int imageChanges; 10 }; 11 void PrettyMenu::changeBackground(std::istream& imgSrc) 12 { 13 lock(&mutex); 14 delete bgImage; 15 ++imageChanges; 16 bgImage = new Image(imgSrc); 17 unlock(&mutex); 18 }從異常安全性的角度看,這個函數(shù)很糟。因為沒有滿足異常安全的兩個條件:
1.不泄露任何資源。上述代碼沒有做到這一點,因為一旦“new Image(imgSrc)”導(dǎo)致異常,對unlock就不會執(zhí)行,于是互斥器就永遠(yuǎn)被把持住了。
2.不允許數(shù)據(jù)破壞。如果“new Image(imgSrc)”拋出異常,bgImage就指向一個已被刪除的對象,imageChanges也已被累加,而其實并沒有新的圖像被成功安裝起來。
?
解決資源泄漏的問題很容易,因為條款13已經(jīng)教會我們“以對象去管理資源”,而條款14也逃入了Lock class作為一種“確保互斥器被及時釋放”的方法:
1 void PrettyMenu::changeBackground(std::istream& imgSrc) 2 { 3 Lock ml(&mutex); //來自條款14; 4 delete bgImage; 5 ++imageChanges; 6 bgImage = new Image(imgSrc); 7 }關(guān)于“資源管理類”如Lock,一個最棒的事情是,它們通常使函數(shù)更短。較少的代碼就是較好的代碼,因為出錯的機(jī)會比較少。
?
異常安全函數(shù)(Exception-safe function)提供以下三個保證之一:
1.基本承諾:如果異常被拋出,程序內(nèi)的任何事物仍然保持在有效狀態(tài)下。沒有任何對象或數(shù)據(jù)結(jié)構(gòu)會因此而敗壞,所有對象都處于一種內(nèi)部前后一致的狀態(tài)(例如所有的class約束條件都繼續(xù)獲得滿足)。然而程序的現(xiàn)實狀態(tài)恐怕不可預(yù)料。如上例changeBackground使得一旦有異常被拋出時,PrettyMenu對象可以繼續(xù)擁有原背景圖像,或是令它擁有某個缺省背景圖像,但客戶無法預(yù)期哪一種情況。如果想知道,它們恐怕必須調(diào)用某個成員函數(shù)以得知當(dāng)時的背景圖像是什么。
2.強烈保證:如果異常被拋出, 程序狀態(tài)不改變。如果函數(shù)成功,就是完全成功,否則,程序會回復(fù)到“調(diào)用函數(shù)之前”的狀態(tài)。
3.不拋擲(nothrow)保證:承諾絕不拋出異常,因為它們總是能夠完成它們原先承諾的功能。作用于內(nèi)置類型(如ints,指針等等)上的所有操作都提供nothrow保證。帶著“空白異常明細(xì)”的函數(shù)必為nothrow函數(shù),其實不盡然
1 int doSomething() throw(); //”空白異常明細(xì)”這并不是說doSomething絕不會拋出異常,而是說如果拋出異常,將是嚴(yán)重錯誤,會有你意想不到的函數(shù)被調(diào)用。實際上doSomething也許完全沒有提供任何異常保證。函數(shù)的聲明式(包括異常明細(xì))并不能告訴你是否它是正確的、可移植的或高效的,也不能告訴你它是否提供任何異常安全性保證。
?
一般而言,應(yīng)該會想提供可實施的最強烈保證。nothrow函數(shù)很棒,但我們很難再c part of c++領(lǐng)域中完全沒有調(diào)用任何一個可能拋出異常的函數(shù)。所以大部分函數(shù)而言,抉擇往往落在基本保證和強烈保證之間。
對changeBackground而言,首先,從一個類型為Image*的內(nèi)置指針改為一個“用于資源管理”的智能指針,第二,重新排列changeBackground內(nèi)的語句次序,使得在更換圖像之后再累加imageChanges。
?
1 class PrettyMenu{ 2 ... 3 std::tr1::shared_ptr<Image> bgImage; 4 ... 5 }; 6 7 void PrettyMenu::changeBackground(std::istream& imgSrc) 8 { 9 Lock ml(&mutex); 10 bgImage.reset(new Image(imgSrc)); 11 ++imageChanges; 12 }?
不再需要手動delete舊圖像,只有在reset在其參數(shù)(也就是“new Image(imgSrc)”的執(zhí)行結(jié)果)被成功生成之后才會被調(diào)用。美中不足的是參數(shù)imgSrc。如果Image構(gòu)造函數(shù)拋出異常,有可能輸入流的讀取記號(read marker)已被移走,而這樣的搬移對程序其余部分是一種可見的狀態(tài)改變。所以在解決這個之前只提供基本點異常安全保證。
作為策略,橋接模式或者叫做PIMPL的模式可以實現(xiàn):
PIMPL模式可以參考我的C++博客。
1 struct PMImpl{ 2 std::tr1::shared_ptr<Image> bgImage; 3 int imageChanges; 4 }; 5 class PrettyMenu{ 6 ... 7 private: 8 Mutex mutex; 9 std::tr1::shared_ptr<PMImpl> pImpl; 10 }; 11 void PrettyMenu::changeBackground(std::istream& imgSrc) 12 { 13 using std::swap; 14 Lock ml(&mutex); 15 std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); 16 pNew->bgImage.reset(new Image(imgSrc)); //修改副本 17 ++pNew->imageChanges; 18 swap(pImpl, pNew); //置換數(shù)據(jù) 19 }在那個副本上做一切必要修改。若有任何修改動作拋出異常,源對象仍然保持未改變狀態(tài)。待所有改變都成功后,再將修改過的副本和原對象在一個不拋出異常的swap中置換
實現(xiàn)上通常是將所有“隸屬對象的數(shù)據(jù)”從原對象放進(jìn)另一個對象內(nèi),然后賦予源對象一個指針,指向那個所謂的實現(xiàn)對象(implementation object,即副本)。
?
◆總結(jié)
1.異常安全函數(shù)(Exception-safe functions)即時發(fā)生異常也不會泄露資源或允許任何數(shù)據(jù)結(jié)構(gòu)破壞。這樣的函數(shù)區(qū)分為三種可能的保證:基本型、強烈型、不拋異常型。
2.“強烈保證”往往能夠以copy-and-swap實現(xiàn)出來,但“強烈保證”并非對所有函數(shù)都可實現(xiàn)或具備現(xiàn)實意義。
3.函數(shù)提供的“異常安全保證”通常最高只等于其所調(diào)用之各個函數(shù)的“異常安全保證”中的最弱者。
?
轉(zhuǎn)載于:https://www.cnblogs.com/hustcser/p/4217938.html
總結(jié)
以上是生活随笔為你收集整理的[Effective C++ --029]为“异常安全”而努力是值得的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ExtJs4学习(七)MVC中的Stor
- 下一篇: [转]C++中的static关键字的总结