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