青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

洛譯小筑

別來無恙,我的老友…
隨筆 - 45, 文章 - 0, 評論 - 172, 引用 - 0
數據加載中……

[ECPP讀書筆記 條目29] 力求代碼做到“異常安全”

異常安全看上去像是孕育生命,但是讓我們先把這種觀點暫時放在一邊。因為在一對戀人結婚之前,討論生育問題還為時尚早。

假設我們正在設計一個表示GUI菜單的類,這種菜單是有背景圖片的,由于這個類設計用于多線程環境中,因此它擁有一個互斥鎖來確保正常的并發控制:

class PrettyMenu {

public:

  ...

  void changeBackground(std::istream& imgSrc);   // 更改背景圖片

  ...

 

private:

  Mutex mutex;                                    // 本對象使用的互斥鎖

 

  Image *bgImage;                                 // 當前的背景圖片

  int imageChanges;                               // 圖片更改的次數

};

下面是PrettyMenuchangeBackground函數實現的一個備選方案:

void PrettyMenu::changeBackground(std::istream& imgSrc)

{

  lock(&mutex);                    // 申請上鎖(同條目14)

 

  delete bgImage;                  // 刪除舊的背景圖片

  ++imageChanges;                  // 更新圖片改變的次數

  bgImage = new Image(imgSrc);     // 裝載新的背景圖片

 

  unlock(&mutex);                  // 解鎖

}

從異常安全的角度來說,這個函數簡直一無是處。異常安全的兩個基本要求,這個函數完全沒有考慮到。

當拋出異常時,異常安全的代碼應該做到:

不要泄漏資源。上面的代碼沒有進行這項檢測,這是因為如果“new Image(imgSrc)”語句產生了異常,那么對unlock的調用則永遠不會兌現,這樣該互斥鎖將永遠不會被解開。

不能讓數據結構遭到破壞。如果“new Image(imgSrc)”拋出異常,bgImage就會指向一個已經銷毀的對象。另外,無論新的圖形是否裝載成功,imageChanges的數值都會增加。(從另一個角度說,舊的圖形肯定是被刪除了,那么你又怎么能確保圖形被“改變”了呢。)

處理資源泄漏問題還是比較簡單的,因為條目13中介紹過如何使用對象來管理資源,條目14中介紹過如何通過Lock類確保互斥鎖在適當的時候被解開:

void PrettyMenu::changeBackground(std::istream& imgSrc)

{

  Lock ml(&mutex);                 // 來自條目14的經驗:

                                   // 申請一個互斥鎖,并確保適時解鎖

  delete bgImage;

  ++imageChanges;

  bgImage = new Image(imgSrc);

}

能夠讓函數變得更短,是諸如Lock這樣的資源管理類最讓人興奮的事情之一。你是否注意到:這里甚至不需要調用unlock。更短的代碼就是更優秀的代碼,因為代碼越短,它帶來錯誤和誤解的機會就越少。這是一條通用的準則。

把資源泄漏問題放在一旁,讓我們把注意力集中在數據結構破壞的問題上。這里我們可以進行一次選擇,但是在進行選擇之前,我們首先要了解所需要的幾個術語。

異常安全函數做出以下三種保證中的一項:

提供基本保證的函數會做出這樣的承諾:如果拋出了一個異常,那么程序中的一切都將保持合法的狀態。沒有任何對象或數據結構會遭到破壞,所有的對象的內部都保持協調的狀態(比如說,類所有的慣例都得到了滿足)。然而,程序的具體狀態可能是無法預知的。比如說,我們可以這樣編寫changeBackground:如果某一時刻拋出一個異常,那么PrettyMenu對象可能繼續使用舊的背景圖片,也可以用某個默認的背景圖片來代替。但是客戶無法做出任何預測。(為了找到答案,客戶大概會調用某個能明示當前背景圖片的成員函數。)

提供增強保證的函數會做出這樣的承諾:如果拋出了一個異常,那么函數的狀態將保持不變。這樣的函數仿佛是一個單一的原子,因為如果調用成功了,它就會大獲全勝;一旦出了差錯,程序狀態將顯示它從來沒有被調用過。

使用提供強保證的的函數要比使用僅提供基本保證的函數簡單一些,這是因為在調用一個提供強保證的函數之后,程序只可能存在兩種狀態:一、按預期進行,函數成功執行;二、程序將保持函數調用前的狀態。反觀提供基本保證的函數,如果在調用它時拋出了一個異常,那么程序可能會處于任何合法的狀態。

提供零異常保證的函數承諾程序決不會拋出異常,因為它們永遠都會按部就班的運行。所有的內建數據類型(int、指針,等等)的操作都是零異常的(提供零異常保證)。這是異常安全代碼必不可少的一個因素。

我們可以假設包含空異常規范的函數是不會拋出異常的,這樣做看上去很合理,但是事實并不一定這樣,請看下面的函數:

int doSomething() throw();          // 請注意:空異常規范

這并不是說doSomething將永遠不會拋出異常,這只是說,如果doSomething拋出了異常,那么此時就出現了一個嚴重的錯誤,同時程序應該調用一個名為unexpected的函數。實際上,doSomething可能根本不會提供任何異常保證。函數的聲明(包括它的異常規范,如果有的話)并不會告訴你它是否正確、是否小巧、是否高效,同時也不會告訴你它他提供了哪個層面上的異常安全保證。所有那些特性都要在函數實現中確定下來,而不是聲明中。

異常安全的代碼必須要提供上述三個層面的保證中的一種。否則它就不是異常安全的。那么你要做的就是:對于所寫的每一個函數都要選擇一個恰當層面的保證。除非我們正在處理異常不安全的老舊代碼(這一點我們稍候再提)。只有當你的“優秀”的需求分析小組提出“你的程序需要泄露資源,并且需要使用破壞的數據結構”時,不提供異常安全保證也許才是一個可行的選擇。

作為一個通用的準則,你會希望提供可行范圍內最強的保證。從異常安全的角度來說,零異常的函數是美妙的,但是如果不去調用可能會拋出異常的函數,你是很難逾越C++中C這一部分的。只要涉及動態內存分配(比如所有的STL容器),如果無法尋找到足夠的內存來滿足當前的要求,那么通常程序都會拋出一個bad_alloc異常(參見條目49)。在可行的時候你應該為函數提供零異常保證,但是對于大多數函數而言,你需要在基本保證和增強保證之間做出選擇。

對于changeBackground,提供增強保證似乎并不是件難事。首先,我們可以改變PrettyMenubgImage數據成員的類型,從一個內建的Image*指針類型轉變為智能資源管理指針(參見條目13)。坦白的說,單獨從防止資源泄漏理論的角度上說,這是一個非常好的設計方案。事實上它簡單地通過使用對象(比如智能指針)來管理資源(也就是遵循了條目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));  // bgImage的內部指針替換為

                                      // ”new Image”語句的結果

  ++imageChanges;

}

請注意這里不需要手動刪除舊圖片,因為這件事情完全由智能指針自行解決了。而且,只有在新圖像成功創建之后,刪除操作才會進行。更精確地說,tr1::shared_ptr::reset函數只有在其參數(“new Image(imgSrc)”的結果)成功創建以后才會得到調用。由于只有在調用reset過程中才會使用delete,因此如果從未進入該函數,就永遠不會用到delete。注意:使用對象(tr1::shared_ptr)來管理資源(動態分配的Image),再次精簡了changeBackground

如前所述,這兩項改變似乎使changeBackground滿足了增強的異常安全保證。可是白璧微瑕,imgSrc參數還有一個小問題。如果Image的構造函數拋出了一個異常,那么輸入流的讀標記很可能已被移動,這樣的移動可能會造成狀態的變化,而這種變化對程序其它部分來說是可見的。在changeBackground解決這一問題之前,它僅僅提供基本的異常安全保證。

然而,讓我們把這個問題暫時放在一旁,假裝changeBackground確實可以提供增強保證。(我相信你可以想出一個辦法來,可以通過改變參數的類型:從輸入流變為包含圖像信息的文件名。)有一個一般化的設計方案,可以使函數做到增強保證,了解這一方案十分重要。該方案一般稱為“復制并swap。”從理論上來講,它非常簡單。為需要修改的對象做一個副本,然后將所有需要做的改變應用于這個副本之上。如果期間任一個修改操作拋出了異常,那么原始的對象依然紋絲未動。在所有改變順利完成之后,通過一次不拋出異常的操作將修改過的對象與原始對象相交換即可(參見條目25)。

上述方案通常這樣實現:將對象的所有數據從“真實的”對象復制到一個獨立實現的對象中,然后為真實對象創建一個指針,將其指向這個實現對象。這通常稱為“pimpl(pointer to implementation,指向實現的指針)慣用法”,條目31中將將解它的一些細節。對于PrettyMenu而言,典型的實現是這樣的:

struct PMImpl {                              // PMImpl = PrettyMenu的實現

  std::tr1::shared_ptr<Image> bgImage;       // 下文將介紹它為什么是結構體

  int imageChanges;                          

};

 

class PrettyMenu {

  ...

 

private:

  Mutex mutex;

  std::tr1::shared_ptr<PMImpl> pImpl;

};

 

void PrettyMenu::changeBackground(std::istream& imgSrc)

{

  using std::swap;                            // 參見條目25

 

  Lock ml(&mutex);                            // 上鎖

 

  std::tr1::shared_ptr<PMImpl>               // 復制對象數據

    pNew(new PMImpl(*pImpl));

 

  pNew->bgImage.reset(new Image(imgSrc));    // 修改副本

  ++pNew->imageChanges;

 

  swap(pImpl, pNew);                          // 交換新數據就位

 

}                                             // 解鎖

在這個示例中,我做出了這樣的選擇:PMImpl是一個結構體而不是類,由于pImpl是私有的,因此PrettyMenu數據的封裝性得以保證。將PMImpl實現為類也不是不可以,但似乎便利性略差。(它同樣會讓面向對象的偏執狂們感到困惑。)如果需要,可以讓PMImpl嵌套在PrettyMenu的內部,但是打包問題與編寫異常安全代碼的問題似乎沒有什么聯系,這不是我們當前所關注的。

有些修改對象狀態的操作,要求要么是完全修改,要么完全不變,此時“復制并swap”策略是完美的。但是,一般情況下,它并不能確保整個函數都做到增強保證。請看下面changeBackground的一個抽象——someFunc,它使用了“復制并swap”策略,但是它包含了兩個其它函數(f1f2)的調用:

void someFunc()

{

  ...                              // 為本地的狀態創建副本

  f1();

  f2();

  ...                              // 交換修改后的狀態就位

}

這里應該很清楚了:如果f1或者f2沒有達到增強保證的要求,那么someFunc就很難滿足增強保證。比如,假設f1僅提供了基本保證,為了讓someFunc能滿足增強保證,就必須要為其編寫額外的代碼,用于調用f1之前確定整個程序的狀態,捕獲f1拋出的所有異常,然后恢復原始的狀態。

如果f1f2都滿足了增強保證,那么事情也不會好到哪去。如果f1運行完成,那么程序的狀態可能經歷了任意的修改過程,因此,一旦f2在此時拋出了一個異常,那么即使f2沒有做任何修改操作,程序的狀態也可能會與someFunc被調用時不一致。

問題在于函數的副作用。只要函數操作僅僅針對本地的狀態(比如說,someFunc僅僅影響到它所調用對象的狀態),提供增強保證就相對簡單些。當函數對于非本地數據存在副作用時,則更加困難些。比如說,如果調用f1引入的副作用是數據庫被修改了,那么讓someFunc滿足異常安全的增強保證就比較困難。一般來說,已經被系統接受的數據庫修改操作是無法撤銷的,這是因為其它的數據庫用戶已經看到了數據庫的新狀態。

不管你情愿與否,諸如這樣的問題會在你期望編寫增強保證的函數時設置重重障礙。另一個問題是:效率。復制并swap策略的核心思想就是修改對象副本的數據,然后通過一個不會拋出異常的操作來交換修改后的數據。這需要為每個需要修改的對象創建出一個副本,這樣做顯然會浪費時間和空間,你也許不會情愿使用這一策略,現實條件有時也會阻止你。增強保證是我們良好的預期目標,只要可行你就應該提供,但是現實中它并不總是可行的。

在增強保證不可行時,你應該提供基本保證。從實用角度說,如果你發現你可以為某些函數提供增強保證,但是由此帶來的效率和復雜度問題讓增強保證變得得不償失。只要你在必要的時候做出了努力使適當的函數滿足了增強保證,那么對于一些函數僅提供基本保證就是無可厚非的。對于大多數函數而言,基本保證已經是合理的、完美的選擇了。

如果你正在編寫一個完全不提供異常安全保證的函數,那么就是另一番景象了。因為在這里完全可以在未證明你無罪之前假定你有罪。你本應該編寫異常安全代碼。但是你也可以為自己做出強有力的辯解。請再次考慮一下someFunc的實現,它調用了兩個函數:f1f2,假設f2完全沒有提供異常安全保證,即使基本保證也沒有,這就意味著一旦f2拋出一個異常,程序可能會在f2的內部發生資源泄露。這意味著f2中可能會有破損的數據結構,比如:排好序的數組可能不再按順序排列,在兩個數據結構之間轉送的對象也可能會丟失數據,等等。這樣someFunc也無力回天。如果someFunc函數調用了沒有提供異常安全保證的函數,那么someFunc自身就無法做出任何保證。

讓我們回到本節開篇時所說的“孕育生命”的問題。一位女性要么就是懷孕,要么就是沒有,絕沒有“部分懷孕”的狀態。類似的,一個軟件系統要么是異常安全的,要么就不是。沒有所謂的“部分異常安全”的狀態存在。在一個系統中,即使只有一個單獨的函數不是異常安全的,那么整個系統也就不是異常安全的。遺憾的是,許多較為古老的C++代碼在編寫的時候完全沒有考慮到異常安全問題,因此當今許多系統便不是異常安全的。新系統中混雜著異常不安全的編寫習慣。

沒有理由去維持現狀。當編寫新代碼或者修改現有代碼的時候,要認真考慮一下如何使之做到異常安全。首先,使用對象管理資源。(依然參見條目13。)這將有效地防止資源泄露。然后對于你要編寫的每個函數確定你要使用哪一層面的異常安全保證,只有在調用古老的、沒有異常安全保證的代碼時才放棄異常安全保證,因為你別無選擇。記錄下你的選擇,這即是為了你的客戶,也是為了今后的維護人員。函數的異常安全保證位于接口的可見部分,因此你應該認真規劃它,就像你認真規劃接口其它部分一樣。

四十年前,人們迷信充斥著goto的代碼是完美的,現在我們卻為了編寫結構化控制流而努力。二十年前,全局的完全可訪問的數據也是高踞神壇,然而當今我們卻在提倡封裝數據。十年前,編寫函數時不去考慮異常的影響的做法倍受追捧,但是今天,我們堅定不渝的編寫異常安全代碼。

歲月荏苒,我們在學習中不斷進步……

時刻牢記

異常安全的函數即使在異常拋出時,也不會帶來資源泄露,同時也不允許數據結構遭到破壞。這類函數提供基本的、增強的、零異常的三個層面的異常安全保證。

增強保證可以通過復制并swap策略來實現,但是增強保證并不是對所有函數都適用。

函數所提供的異常安全保證通常不會強于其所調用函數中保證層次最弱的一個。

posted on 2007-10-04 21:53 ★ROY★ 閱讀(1480) 評論(5)  編輯 收藏 引用 所屬分類: Effective C++

評論

# re: 【讀書筆記】[Effective C++第3版][第29條] 力求使代碼做到“異常安全”  回復  更多評論   

強悍!能做這么多筆記
2007-10-05 10:06 | Minidx全文檢索

# re: 【讀書筆記】[Effective C++第3版][第29條] 力求使代碼做到“異常安全”  回復  更多評論   

嘿嘿 您損我。。。
2007-10-05 11:33 | ★ROY★

# re: 【讀書筆記】[Effective C++第3版][第29條] 力求使代碼做到“異常安全”  回復  更多評論   

呵呵,沒有那個意思
整理一下可以拿來當作論文發表了~
2007-10-05 16:09 | Minidx全文檢索

# re: 【讀書筆記】[Effective C++第3版][第29條] 力求使代碼做到“異常安全”  回復  更多評論   

崇拜
2007-10-05 16:46 | alien

# re: 【讀書筆記】[Effective C++第3版][第29條] 力求使代碼做到“異常安全”  回復  更多評論   

"異常安全看上去像是孕育生命"
"一位女性要么就是懷孕,要么就是沒有,絕沒有“部分懷孕”的狀態。"
scott meyers 很幽默

Except 異常 expecting 懷孕~~
2007-10-08 14:03 | Icat
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美国产高潮xxxx1819| 久久激情五月丁香伊人| 欧美日韩不卡合集视频| 亚洲精品一区在线观看香蕉| 欧美激情视频网站| 欧美精品久久一区二区| 亚洲手机在线| 欧美在线网址| 日韩午夜黄色| 午夜精品久久久久久久蜜桃app | 亚洲一区在线观看视频| 国产一区二区三区久久| 欧美高清影院| 欧美色中文字幕| 久久久久久999| 欧美成人国产一区二区| 亚洲欧美日本日韩| 久久久蜜桃一区二区人| 中文精品视频| 久久久久久久999精品视频| 日韩亚洲欧美成人| 欧美一区在线直播| 99国内精品久久| 欧美一区日韩一区| 夜夜夜久久久| 久久午夜激情| 性色av一区二区三区在线观看| 久久久久一区二区| 欧美亚洲网站| 欧美日韩精品免费观看| 久久影视精品| 久久精品一区四区| 免费h精品视频在线播放| 亚洲欧美另类国产| 欧美va亚洲va香蕉在线| 欧美在线三级| 国产精品久久久久久久久免费樱桃| 久热精品视频在线免费观看| 欧美日韩国产综合视频在线| 欧美成人精品h版在线观看| 欧美日韩中文在线观看| 欧美第十八页| 狠狠色丁香久久综合频道| 亚洲手机成人高清视频| 99国产精品久久久久老师| 久久男女视频| 久久九九免费视频| 国产日韩亚洲欧美| 亚洲一二三区视频在线观看| 亚洲久久一区| 欧美大片在线观看一区二区| 久久久91精品国产| 国产日韩免费| 亚洲欧美色婷婷| 亚洲在线不卡| 国产精品大片| 一区二区欧美国产| 亚洲一区高清| 国产精品久久毛片a| 亚洲视频免费| 欧美一区二区三区精品电影| 国产精品丝袜91| 亚洲自拍另类| 久久精品成人| 国内外成人免费激情在线视频网站 | 亚洲国产成人久久| 欧美成年人网站| 亚洲经典在线看| 亚洲国产一区二区三区在线播 | 日韩午夜在线电影| 亚洲欧美日韩一区二区三区在线观看| 欧美日本一区二区高清播放视频| 亚洲欧洲精品一区二区三区不卡 | 国产精品日韩久久久久| 亚洲欧美日韩精品在线| 久久九九全国免费精品观看| 国内精品久久久久影院优| 久久久综合网站| 亚洲国产精品一区制服丝袜| 一区二区久久久久| 国产精品一区二区女厕厕| 欧美在线免费视屏| 欧美激情综合| 午夜久久影院| 一色屋精品亚洲香蕉网站| 欧美成人一二三| 亚洲一区精品在线| 美女成人午夜| 亚洲一区二区网站| 红桃av永久久久| 欧美日韩一区二区三区免费| 新狼窝色av性久久久久久| 欧美激情亚洲激情| 亚洲欧美在线磁力| 亚洲电影观看| 国产精品一卡二| 久久精品99| 国产精品美腿一区在线看 | 一区二区三区久久| 久久久久久久综合色一本| 日韩小视频在线观看| 国产精品久久久久久一区二区三区 | 蜜臀a∨国产成人精品 | 亚洲国产91| 午夜精品一区二区三区在线播放 | 亚洲一区二区三区精品在线| 国产综合视频在线观看| 欧美日韩精品在线| 久久深夜福利| 午夜视频在线观看一区| 亚洲激情社区| 鲁大师影院一区二区三区| 亚洲一区二区三| 亚洲国内自拍| 狠狠爱成人网| 国产日韩精品一区二区三区| 欧美日韩国产在线看| 久久综合一区二区| 欧美专区福利在线| 亚洲桃花岛网站| 亚洲理论电影网| 亚洲激情第一区| 欧美成人午夜视频| 久久久久久9999| 小嫩嫩精品导航| 亚洲综合国产激情另类一区| 亚洲精品小视频| 亚洲欧洲日本在线| 亚洲第一视频| 亚洲激情第一页| 亚洲国产日韩在线| 亚洲国产女人aaa毛片在线| 国产一区二区中文| 国产亚洲精品综合一区91| 国产精品视频xxx| 国产精品一区视频| 国产日韩在线一区| 国产欧美日韩免费| 国产视频在线观看一区| 国产日韩欧美在线播放不卡| 国产精品视频999| 国产伦精品一区| 国产精品无码永久免费888| 国产精品免费小视频| 国产精品揄拍500视频| 国产精品一区二区三区观看| 国产精品永久免费视频| 国产欧美一区在线| 韩国在线一区| 亚洲人成网站精品片在线观看| 亚洲国产成人久久综合一区| 91久久精品一区| 一区二区三区久久久| 亚洲欧美日韩系列| 久久精品人人做人人爽| 久久综合久久美利坚合众国| 米奇777超碰欧美日韩亚洲| 欧美大片免费观看在线观看网站推荐| 欧美电影免费观看| 亚洲精品一区二区在线| 亚洲在线视频观看| 久久久久五月天| 欧美日韩精品在线视频| 欧美日韩一区三区四区| 欧美jizz19性欧美| 欧美日韩理论| 国产视频久久网| 亚洲激情黄色| 亚洲欧美日韩一区二区三区在线观看| 久久精品成人一区二区三区蜜臀| 欧美福利影院| 亚洲一区在线播放| 久久手机免费观看| 欧美午夜宅男影院在线观看| 国内外成人免费激情在线视频| 亚洲精品在线看| 久久精品一区二区| 日韩一区二区精品| 久久久久久9999| 国产精品久久久久久久久久三级| 伊人春色精品| 亚洲永久免费av| 亚洲福利专区| 午夜伦理片一区| 欧美日韩午夜剧场| 亚洲成在线观看| 亚洲女人天堂av| 亚洲高清av在线| 欧美在线1区| 国产精品久久久久秋霞鲁丝| 亚洲激情午夜| 久久综合九色欧美综合狠狠| 亚洲无亚洲人成网站77777| 欧美成人一区二区三区在线观看| 国产精品一区免费观看| 正在播放亚洲一区| 亚洲福利视频在线| 久久久精品国产一区二区三区| 国产精品久久午夜| 亚洲一区成人|