亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

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

2019-11-17 05:11:59
字體:
來源:轉載
供稿:網友

  異常安全(Exception safety)有點像懷孕(PRegnancy)……但是,請把這個想法先控制一會兒。我們還不能真正地議論生育(reprodUCtion),直到我們排除萬難渡過求愛時期(courtship)。(此段作者使用的 3 個詞均有雙關含義,pregnancy 也可理解為富有意義,reproduction 也可理解為再現,再生,courtship 也可理解為爭取,謀求。為了與后面的譯文對應,故按照現在的譯法。——譯者注)

  假設我們有一個類,代表帶有背景圖像的 GUI 菜單。這個類被設計成在多線程環境中使用,所以它有一個用于并行控制(concurrency control)的互斥體(mutex):

class PrettyMenu {
public:
 ...
 void changeBackground(std::istream& imgSrc); // change background
 ... // image

private:

 Mutex mutex; // mutex for this object

 Image *bgImage; // current background image
 int imageChanges; // # of times image has been changed
};
  考慮這個 PrettyMenu 的 changeBackground 函數的可能的實現:

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
 lock(&mutex); // acquire mutex (as in Item 14)

 delete bgImage; // get rid of old background
 ++imageChanges; // update image change count
 bgImage = new Image(imgSrc); // install new background

 unlock(&mutex); // release mutex
}
  從異常安全的觀點看,這個函數爛到了極點。異常安全有兩條要求,而這里全都沒有滿足。

  當一個異常被拋出,異常安全的函數應該:

  ·沒有資源泄露。上面的代碼沒有通過這個測試,因為假如 "new Image(imgSrc)" 表達式產生一個異常,對 unlock 的調用就永遠不會執行,而那個互斥體也將被永遠掛起。

  ·不答應數據結構惡化。假如 "new Image(imgSrc)" 拋出異常,bgImage 被遺留下來指向一個被刪除對象。另外,盡管并沒有將一張新的圖像設置到位,imageChanges 也已經被增加。(在另一方面,舊的圖像被明確地刪除,所以我料想你會爭辯說圖像已經被“改變”了。)

  規避資源泄露問題比較輕易,我們以前解釋了如何使用對象治理資源,也討論了引進 Lock 類作為一種時尚的確?;コ怏w被釋放的方法:

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
 Lock ml(&mutex); // from Item 14: acquire mutex and
 // ensure its later release
 delete bgImage;
 ++imageChanges;
 bgImage = new Image(imgSrc);
}
  關于像 Lock 這樣的資源治理類的最好的事情之一是它們通常會使函數變短??吹綄?unlock 的調用不再需要了嗎?作為一個一般的規則,更少的代碼就是更好的代碼。因為在改變的時候這樣可以較少誤入歧途并較少產生誤解。

  隨著資源泄露被我們甩在身后,我們可以把我們的注重力集中到數據結構惡化。在這里我們有一個選擇,但是在我們能選擇之前,我們必須先面對定義我們的選擇的術語。 異常安全函數提供下述三種保證之一:

  ·函數提供基本保證(the basic guarantee),允諾假如一個異常被拋出,程序中剩下的每一件東西都處于合法狀態。沒有對象或數據結構被破壞,而且所有的對象都處于內部調和狀態(所有的類不變量都被滿足)。然而,程序的精確狀態可能是不可預期的。例如,我們可以重寫 changeBackground,以致于假如一個異常被拋出,PrettyMenu 對象可以繼續保留原來的背景圖像,或者它可以持有某些缺省的背景圖像,但是客戶無法預知到底是哪一個。(為了查明這一點,他們大概必須調用某個可以告訴他們當前背景圖像是什么的成員函數。)

  ·函數提供強力保證(the strong guarantee),允諾假如一個異常被拋出,程序的狀態不會發生變化。調用這樣的函數在感覺上是極其微弱的,假如它們成功了,它們就完全成功,假如它們失敗了,程序的狀態就像它們從沒有被調用過一樣。

  ·與提供強力保證的函數一起工作比與只提供基本保證的函數一起工作更加輕易,因為調用提供強力保證的函數之后,僅有兩種可能的程序狀態:像預期一樣成功執行了函數,或者繼續保持函數被調用時當時的狀態。與之相比,假如調用只提供基本保證的函數引發了異常,程序可能存在于任何合法的狀態。

  函數提供不拋出保證(the nothrow guarantee),允諾決不拋出異常,因為它們只做它們答應要做的。所有對內建類型(例如,ints,指針,等等)的操作都是不拋出(nothrow)的(也就是說,提供不拋出保證)。這是異常安全代碼中必不可少的基礎構件。

  假定一個帶有空的異常規格(exception specification)的函數是不拋出的似乎是合理的,但這不一定正確的。例如,考慮這個函數:


int doSomething() throw(); // note empty exception spec.
  這并不是說 doSomething 永遠不會拋出異常;而是說假如 doSomething 拋出一個異常,它就是一個嚴重的錯誤,應該調用 uneXPected 函數 [1]。實際上,doSomething 可能根本不提供任何異常保證。一個函數的聲明(假如有的話,也包括它的異常規格(exception specification))不能告訴你一個函數是否正確,是否可移植,或是否高效,而且,即便有,它也不能告訴你它會提供哪一種異常安全保證。所有這些特性都由函數的實現決定,而不是它的聲明能決定的。

  [1] 關于 unexpected 函數的資料,可以求助于你中意的搜索引擎或包羅萬象的 C++ 課本。(你或許有幸搜到 set_unexpected,這個函數用于指定 unexpected 函數。)

  異常安全函數必須提供上述三種保證中的一種。假如它沒有提供,它就不是異常安全的。于是,選擇就在于決定你寫的每一個函數究竟要提供哪種保證。除非要處理遺留下來的非異常安全的代碼(稍后我們要討論這個問題),只有當你的最高明的需求分析團隊為你的應用程序識別出的一項需求就是泄漏資源以及運行于被破壞的數據結構之上時,不提供異常安全保證才能成為一個選項。

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

  在 changeBackground 的情況下,提供差不多的強力保證并不困難。首先,我們將 PrettyMenu 的 bgImage 數據成員的類型從一個內建的 Image* 指針改變為 Item 13 中描述的智能資源治理指針中的一種。坦白地講,在預防資源泄漏的基本原則上,這完全是一個好主意。它幫助我們提供強大的異常安全保證的事實進一步加強了這樣的觀點——使用對象(諸如智能指針)治理資源是良好設計的基礎。在下面的代碼中,我展示了 tr1::shared_ptr 的使用,因為當進行通常的拷貝時它的更符合直覺的行為使得它比 auto_ptr 更可取。

  第二,我們重新排列 changeBackground 中的語句,以致于直到圖像發生變化,才增加 imageChanges。這是一個很好的策略——直到某件事情真正發生了,再改變一個對象的狀態來表示某事已經發生。

  這就是修改之后的代碼:

class PrettyMenu {
 ...
 std::tr1::shared_ptr<Image> bgImage;
 ...
};

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
 Lock ml(&mutex);

 bgImage.reset(new Image(imgSrc)); // replace bgImage’s internal
 // pointer with the result of the
 // "new Image" expression
 ++imageChanges;
}
  注重這里不再需要手動刪除舊的圖像,因為在智能指針內部已經被處理了。此外,只有當新的圖像被成功創建了刪除行為才會發生。更準確地說,只有當 tr1::shared_ptr::reset 函數的參數("new Image(imgSrc)" 的結果)被成功創建了,這個函數才會被調用。只有在對 reset 的調用的內部才會使用 delete,所以假如這個函數從來不曾進入,delete 就從來不曾使用。同樣請注重一個治理資源(動態分配的 Image)的對象(tr1::shared_ptr)的使用再次縮短了 changeBackground 的長度。

  正如我所說的,這兩處改動差不多有能力使 changeBackground 提供強力異常安全保證。美中不足的是什么呢?參數 imgSrc。假如 Image 的構造函數拋出一個異常,輸入流(input stream)的讀標記(read marker)可能已經被移動,而這樣的移動就成為對程序的其它部分來說可見的一個狀態的變化。直到 changeBackground 著手解決這個問題之前,它只能提供基本異常安全保證。

  無論如何,讓我們把它放在一邊,并且依然假裝 changeBackground 可以提供強力保證。(我相信你至少能用一種方法做到這一點,或許可以通過將它的參數從一個 istream 改變到包含圖像數據的文件的文件名。)有一種通常的設計策略可以有代表性地產生強力保證,而且熟悉它是非常必要的。這個策略被稱為 "copy and swap"。它的原理很簡單。先做出一個你要改變的對象的拷貝,然后在這個拷貝上做出全部所需的改變。假如改變過程中的某些操作拋出了異常,最初的對象保持不變。在所有的改變完全成功之后,將被改變的對象和最初的對象在一個不會拋出異常的操作中進行交換。 這通常通過下面的方法實現:將每一個對象中的全部數據從“真正的”對象中放入到一個單獨的實現對象中,然后將一個指向實現對象的指針交給真正對象。這通常被稱為 "pimpl idiom",Item 31 描述了它的一些細節。對于 PrettyMenu 來說,它一般就像這樣:

struct PMImpl { // PMImpl = "PrettyMenu
 std::tr1::shared_ptr<Image> bgImage; // Impl."; see below for
 int imageChanges; // why it’s a struct
};

class PrettyMenu {
 ...

private:
 Mutex mutex;
 std::tr1::shared_ptr<PMImpl> pImpl;
};

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
 using std::swap; // see Item 25

 Lock ml(&mutex); // acquire the mutex

 std::tr1::shared_ptr<PMImpl> // copy obj. data
 pNew(new PMImpl(*pImpl));

 pNew->bgImage.reset(new Image(imgSrc)); // modify the copy
 ++pNew->imageChanges;

 swap(pImpl, pNew); // swap the new
 // data into place

} // release the mutex
  在這個例子中,我選擇將 PMImpl 做成一個結構體,而不是類,因為通過讓 pImpl 是 private 就可以確保 PrettyMenu 數據的封裝。將 PMImpl 做成一個類雖然有些不那么方便,卻沒有增加什么好處。(這也會使有面向對象潔癖者走投無路。)假如你愿意,PMImpl 可以嵌套在 PrettyMenu 內部,像這樣的打包問題與我們這里所關心的寫異常安全的代碼的問題沒有什么關系。

  copy-and-swap 策略是一種全面改變或絲毫不變一個對象的狀態的極好的方法,但是,在通常情況下,它不能保證全部函數都是強力異常安全的。為了弄清原因,考慮一個 changeBackground 的抽象化身—— someFunc,它使用了 copy-and-swap,但是它包含了對另外兩個函數(f1 和 f2)的調用:


void someFunc()
{
 ... // make copy of local state
 f1();
 f2();
 ... // swap modified state into place
}
  很明顯,假如 f1 或 f2 低于強力異常安全,someFunc 就很難成為強力異常安全的。例如,假設 f1 僅提供基本保證。為了讓 someFunc 提供強力保證,它必須寫代碼在調用 f1 之前測定整個程序的狀態,并捕捉來自 f1 的所有異常,然后恢復到最初的狀態。

  即使 f1 和 f2 都是強力異常安全的,事情也好不到哪去。假如 f1 運行完成,程序的狀態已經發生了毫無疑問的變化,所以假如隨后 f2 拋出一個異常,即使 f2 沒有改變任何東西,程序的狀態也已經和調用 someFunc 時不同。

  問題在于副作用。只要函數僅對局部狀態起作用(例如,someFunc 僅僅影響調用它的那個對象的狀態),它提供強力保證就相對輕易。當函數的副作用影響了非局部數據,它就會困難得多。例如,假如調用 f1 的副作用是改變數據庫,讓 someFunc 成為強力異常安全就非常困難。一般情況下,沒有辦法撤銷已經提交的數據庫變化,其他數據庫客戶可能已經看見了數據庫的新狀態。

  類似這樣的問題會阻止你為函數提供強力保證,即使你希望去做。另一個問題是效率。copy-and-swap 的要點是這樣一個想法:改變一個對象的數據的拷貝,然后在一個不會拋出異常的操作中將被改變的數據和原始數據進行交換。這就需要做出每一個要改變的對象的拷貝,這可能會用到你不能或不情愿動用的時間和空間。強力保證是非常值得的,當它可用時你應該提供它,除非在它不能 100% 可用的時候。

  當它不可用時,你就必須提供基本保證。在實踐中,你可能會發現你能為某些函數提供強力保證,但是效率和復雜度的成本使得它難以支持大量的其它函數。無論何時,只要你作出過一個提供強力保證的合理的成果,就沒有人會因為你僅僅提供了基本保證而站在批評你的立場上。對于很多函數來說,基本保證是一個完全合理的選擇。

  假如你寫了一個根本沒有提供異常安全保證的函數,事情就不同了,因為在這一點上有罪推定是合情合理的,直到你證實自己是清白的。你應該寫出異常安全的代碼。除非你能做出有說服力的答辯。請再次考慮 someFunc 的實現,它調用了函數 f1 和 f2。假設 f2 根本沒有提供異常安全保證,甚至沒有基本保證。這就意味著假如 f2 發生一個異常,程序可能會在 f2 內部泄漏資源。這也意味著 f2 可能會惡化數據結構,例如,已排序數組可能不再排序,一個正在從一個數據結構傳送到另一個數據結構去的對象可能丟失,等等。沒有任何辦法可以讓 someFunc 能彌補這些問題。假如 someFunc 調用的函數不提供異常安全保證,someFunc 本身就不能提供任何保證。

  請答應我回到懷孕的話題。一個女性或者懷孕或者沒有。局部懷孕是絕不可能的。與此相似,一個軟件或者是異常安全的或者不是。沒有像一個局部異常安全的系統這樣的東西。一個系統即使只有一個函數不是異常安全的,那么系統作為一個整體就不是異常安全的,因為調用那個函數可能發生泄漏資源和惡化數據結構。不幸的是,很多 C++ 的遺留代碼在寫的時候沒有留意異常安全,所以現在的很多系統都不是異常安全的。它們混合了用非異常安全(exception-unsafe)的方式書寫的代碼。

  沒有理由讓事情的這種狀態永遠持續下去。當書寫新的代碼或改變現存代碼時,要仔細考慮如何使它異常安全。以使用對象治理資源開始。這樣可以防止資源泄漏。接下來,決定三種異常安全保證中的哪一種是你實際上能夠為你寫的每一個函數提供的最強的保證,只有當你不調用遺留代碼就別無選擇的時候,才能滿足于沒有保證。既是為你的函數的客戶也是為了將來的維護人員,文檔化你的決定。一個函數的異常安全保證是它的接口的可見部分,所以你應該特意選擇它,就像你特意選擇一個函數接口的其它方面。

  四十年前,到處都是 goto 的代碼被尊為最佳實踐?,F在我們為書寫結構化控制流程而奮斗。二十年前,全局可訪問數據被尊為最佳實踐?,F在我們為封裝數據而奮斗,十年以前,寫函數時不必考慮異常的影響被尊為最佳實踐。現在我們為寫異常安全的代碼而奮斗。

  時光在流逝。我們生活著。我們學習著。

  Things to Remember

  ·即使當異常被拋出時,異常安全的函數不會泄露資源,也不答應數據結構被惡化。這樣的函數提供基本的,強力的,或者不拋出保證。

  ·強力保證經??梢酝ㄟ^ copy-and-swap 被實現,但是強力保證并非對所有函數都可用。

  ·一個函數通常能提供的保證不會強于他所調用的函數中最弱的保證。 更多文章 更多內容請看C/C++技術專題  FreeBSD系統安全治理  linux服務器的安全性能專題,或

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲色图35p| 成人国产亚洲精品a区天堂华泰| 隔壁老王国产在线精品| 亚洲一区亚洲二区亚洲三区| 欧美自拍大量在线观看| 日韩免费在线免费观看| 色综久久综合桃花网| 久久精品国产精品亚洲| 国产亚洲欧洲高清一区| 国产a∨精品一区二区三区不卡| 国产69久久精品成人看| 高跟丝袜一区二区三区| 日韩av在线天堂网| 亚洲老板91色精品久久| 国产精品久久综合av爱欲tv| 亚洲男人的天堂在线| 亚洲精品欧美日韩专区| 欧美日韩在线一区| 亚洲欧美日本精品| 国内伊人久久久久久网站视频| 亚洲精品一区二三区不卡| 精品国产自在精品国产浪潮| 精品人伦一区二区三区蜜桃网站| 欧美日韩国产页| 久久久精品一区二区| 91tv亚洲精品香蕉国产一区7ujn| 国产精品自拍视频| 久久精品中文字幕一区| 清纯唯美亚洲综合| 欧美激情精品久久久久久黑人| 精品成人国产在线观看男人呻吟| 亚洲影院色无极综合| 97视频在线观看视频免费视频| 国产成人精品视频| 97超视频免费观看| 亚洲日本aⅴ片在线观看香蕉| 亚洲a级在线观看| 午夜精品福利电影| 久久久噜噜噜久久久| 在线视频亚洲欧美| 欧美色视频日本高清在线观看| 亚洲国产成人久久综合一区| 精品偷拍各种wc美女嘘嘘| 国产精品极品美女在线观看免费| 欧美一区在线直播| 国产精品久久国产精品99gif| 久久精品视频播放| 性视频1819p久久| 国产精品福利无圣光在线一区| 中文字幕精品av| 日韩av在线影院| 欧美中文在线免费| 亚洲天堂网站在线观看视频| 欧美贵妇videos办公室| 国产男人精品视频| 亚洲欧洲在线免费| 欧美精品一区在线播放| 欧美精品免费在线观看| 成人欧美一区二区三区黑人孕妇| 中文字幕亚洲无线码在线一区| 国产在线拍揄自揄视频不卡99| 久久久久久中文字幕| 狠狠躁天天躁日日躁欧美| 欧美日韩国产黄| 91精品视频一区| 国产精品视频资源| 欧美成aaa人片免费看| 黑人巨大精品欧美一区免费视频| 日韩免费观看在线观看| 国产z一区二区三区| 亚洲精品乱码久久久久久金桔影视| 欧美电影免费在线观看| 亚洲欧美中文字幕| 国内偷自视频区视频综合| 成人亚洲激情网| 欧美日韩成人黄色| 精品色蜜蜜精品视频在线观看| 美日韩丰满少妇在线观看| 亚洲少妇激情视频| 91a在线视频| 国产精品扒开腿做爽爽爽的视频| 日本韩国欧美精品大片卡二| 国产精品网红福利| 亚洲精品永久免费精品| 欧美性xxxx在线播放| 久久伊人91精品综合网站| 久久亚洲一区二区三区四区五区高| 激情av一区二区| 欧美黄色成人网| 欧美一级在线亚洲天堂| 精品中文字幕在线观看| 国产午夜精品麻豆| 国产精品丝袜视频| 欧美电影免费观看| 亚洲美女自拍视频| 欧美壮男野外gaytube| 国产精品丝袜白浆摸在线| 欧美巨大黑人极品精男| 91精品视频免费看| 久久视频免费观看| 欧美视频一二三| 欧美日韩亚洲精品内裤| 国产成人精品久久久| 亚洲精品97久久| 国产精品久久久久久久电影| 日本精品久久久久影院| 欧美性猛交xxxx免费看漫画| 欧美激情视频一区二区三区不卡| 国产成人综合亚洲| 亚洲www在线观看| 久热精品在线视频| 国产精品2018| 亚洲免费电影一区| 欧美性高跟鞋xxxxhd| 国产精品三级美女白浆呻吟| 久久久久久久久久久久av| 欧美老肥婆性猛交视频| 欧美大荫蒂xxx| 欧美精品18videos性欧美| 国产一区欧美二区三区| 国产欧美在线观看| 日本欧美精品在线| 日本欧美国产在线| 神马久久桃色视频| 中文字幕无线精品亚洲乱码一区| 久久精品亚洲一区| 日韩毛片中文字幕| 欧美插天视频在线播放| 国产精品日韩欧美| 欧美亚洲另类制服自拍| 国产精品久久久久免费a∨| 久久久最新网址| 久久久久久久久久久91| 欧美成人高清视频| 俺去亚洲欧洲欧美日韩| 国产区精品视频| 欧美国产第二页| 国产精品视频自拍| 日韩最新中文字幕电影免费看| 国产精品狠色婷| 欧美人与性动交a欧美精品| 欧美日韩国产一区在线| 性欧美在线看片a免费观看| 亚洲偷欧美偷国内偷| 成人激情视频在线| 国产精品男女猛烈高潮激情| 午夜精品一区二区三区视频免费看| 66m—66摸成人免费视频| 裸体女人亚洲精品一区| 欧美日韩激情小视频| 亚洲黄色www网站| 97精品国产97久久久久久春色| 情事1991在线| 久久久久久成人| 日本中文字幕不卡免费| 日韩精品免费一线在线观看| 北条麻妃一区二区三区中文字幕| 国产精品狼人色视频一区| 成人黄色大片在线免费观看| 国产日韩欧美在线视频观看| 亚洲欧美国产精品va在线观看| 国产精品 欧美在线| 久久久成人av| 欧美日韩国产一区二区三区|