C++異常安全


轉自:http://www.cnblogs.com/zgfLawliet/p/3417308.html

 

異常安全的代碼是指,滿足兩個條件

1異常中立性 :

是指當你的代碼(包括你調用的代碼)引發異常時,這個異常 能保持原樣傳遞到外層調用代碼

2.異常安全性:

  • 拋出異常后,資源不泄露,
  • 拋出異常后,不會使原有數據惡化(例如正常指針變野指針)
  • 少些try catch,因為大量的try catch會影響代碼邏輯。導致代碼丑陋混亂不優雅

 

一段代碼要具有異常安全性,必須同時具有異常中立性和一定等級的異常安全性保證

異常安全的等級一般有:

1,函數提供基本保證(the basic guarantee)(不會發生內存泄漏並且程序內的每個對象都處在合法的狀態,沒有流錯位,沒有野指針,但是不是每個對象的精確狀態是可以預期的,如可能發生異常后,指針處在空指針狀態,或者某種默認狀態,但是客戶無法預知到底是哪一個),對於達成基本保證可以多使用智能指針來管理資源

2,函數提供強力保證(the strong guarantee),強力保證含義是,成功或者回滾保證,發生異常的函數對程序來說,沒有任何改動,提供發生異常時候的回滾機制。

調用提供強力保證的函數之后,僅有兩種可能的程序狀態:像預期一樣成功執行了函數,或者函數回滾繼續保持函數被調用時當時的狀態。與之相比,如果調用只提供基本保證的函數引發了異常,程序可能存在於任何合法的狀態。

函數提供強力保證的有效解決辦法是

copy-and-swap:

先做出一個你要改變的對象的copy,然后在這個copy上做出全部所需的改變。如果改變過程中的某些操作拋出了異常,最初的對象保持不變。在所有的改變完全成功之后,將被改變的對象和最初的對象在一個不會拋出異常的操作中進行swap。

3. 函數有不拋出保證(the nothrow guarantee),對於所有對內建類型(例如,ints,指針,等等)的操作都是不拋出(nothrow)的(也就是說,提供不拋出保證)。這是異常安全代碼中必不可少的基礎構件。

注意事項:

異常安全 最關鍵的是:swap ctor dctor 不發生異常保證,只有成功或者終止程序兩種狀態

一個函數的異常安全等級,是取決於它所調用的函數中最低異常安全等級的函數。

C++11新增了noexcept關鍵字,在void func() noexcept{}noexcept保證了這個函數不會拋出異常,只有終止程序和成功執行兩種狀態。

noexcept可以接受一個常量表達式,noexcept(constexpr。。。)當常量表達式為轉換為true說明該函數保證不拋出異常。

從異常安全的觀點看,不拋出的函數(nothrow functions)是極好的,但是在 C++ 的 C 部分之外部不調用可能拋出異常的函數簡直就是寸步難行。使用動態分配內存的任何東西(例如,所有的 STL 容器)如果不能找到足夠的內存來滿足一個請求,在典型情況下,它就會拋出一個 bad_alloc 異常。只要你能做到就提供不拋出保證,但是對於大多數函數,選擇是在基本的保證和強力的保證之間的。

但是,不是所有函數都能做出異常保證的,考慮這樣一個函數,函數內部的函數內是一個對數據庫的操作,一旦異常發生,難以撤銷對數據庫的更改。如果想對這樣的函數做到異常的strong guarantee保證,就是非常困難度事情。

所以對於只對局部變量改變的函數保證異常安起會相對比較容易。如果函數的操作中牽扯到全局變量等等,就變得困難的多。

解決異常安全的好辦法:

1,多使用RAII,使用智能指針來管理內存。由於unwind機制的保證,當異常發生時,函數棧內已構造的局部對象的析構函數會被一一調用,在析構函數內釋放資源,也就杜絕了內存泄漏的問題。

2,做好程序設計。特別是異常發生時的回滾機制的正確使用,copy-and-swap是有效的方法。

3,注意需要異常保證的函數內部的調用函數,異常安全等級是以有最低等級異常保證的函數確定的。

一個系統即使只有一個函數不是異常安全的,那么系統作為一個整體就不是異常安全的,因為調用那個函數可能發生泄漏資源和惡化數據結構。

4,對於一些需要保證性能的程序,在提供基本的異常安全時,要注意,棧解退機制只是調用析構函數,對於內置類型的操作不會被回滾,所以。像起累加器作用的一些內置類型變量,應該注意在函數成功執行后再進行累加。避免數據結構惡化。重新分配資源給原本已經持有資源的變量,應該先清空釋放變量的資源,指針再設置為nullptr,防止資源重新分配過程中拋出異常,導致指針變為野指針的問題。

5,流對象,資源對象,new對象,不應該直接作為參數,一旦拋出異常,就可能會導致嚴重的問題,函數也許會被錯誤的執行,資源也許會泄漏。對於函數參數和函數內使用的全局變量,應該保證在進入函數內部是是正常狀態。

6.減少全局變量的使用,對包含全局變量的函數做異常安全是比較困難的事情,棧解退也只對局部變量起效果。

7,如果不知道如何處理異常,就不要捕獲異常,直接終止比吞掉異常不處理要好

8.保證 構造 析構 swap不會失敗

這里有個注意事項:

在構造函數中,如果拋出異常,是不會調用當前正在構造的類的析構函數的,因為當前正在構造的類沒有構造完成,只會析構已經構造完成成員和父類,So,極易導致內存泄漏,這里要謹慎處理,使用RAII,智能指針,noexcept保證不會拋出異常和惡化數據。

 

參考文章:

1:對象生死劫-構造函數和析構函數異常

http://blog.csdn.net/leadzen/article/details/1783116

2:C++箴言:爭取異常安全的代碼

http://dev.yesky.com/490/2087990.shtml

3:如何編寫異常安全的代碼

http://blog.csdn.net/wingfiring/article/details/660900


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM