異常安全(Exception safety)有點像懷孕(pregnancy)……但是,請把這個想法先控制一會兒。我們還不能真正地議論生育(reproduction),直到我們排除萬難渡過求愛時期(courtship)。(此段作者使用的 3 個詞均有雙關(guān)含義,pregnancy 也可理解為富有意義,reproduction 也可理解為再現(xiàn),再生,courtship 也可理解為爭取,謀求。為了與后面的譯文對應(yīng),故按照現(xiàn)在的譯法。——譯者注)
假設(shè)我們有一個類,代表帶有背景圖像的 GUI 菜單。這個類被設(shè)計成在多線程環(huán)境中使用,所以它有一個用于并行控制(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 函數(shù)的可能的實現(xiàn):
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
}
從異常安全的觀點看,這個函數(shù)爛到了極點。異常安全有兩條要求,而這里全都沒有滿足。
當(dāng)一個異常被拋出,異常安全的函數(shù)應(yīng)該:
·沒有資源泄露。上面的代碼沒有通過這個測試,因為如果 "new Image(imgSrc)" 表達式產(chǎn)生一個異常,對 unlock 的調(diào)用就永遠不會執(zhí)行,而那個互斥體也將被永遠掛起。
·不允許數(shù)據(jù)結(jié)構(gòu)惡化。如果 "new Image(imgSrc)" 拋出異常,bgImage 被遺留下來指向一個被刪除對象。另外,盡管并沒有將一張新的圖像設(shè)置到位,imageChanges 也已經(jīng)被增加。(在另一方面,舊的圖像被明確地刪除,所以我料想你會爭辯說圖像已經(jīng)被“改變”了。)
規(guī)避資源泄露問題比較容易,我們以前解釋了如何使用對象管理資源,也討論了引進 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);
}
關(guān)于像 Lock 這樣的資源管理類的的事情之一是它們通常會使函數(shù)變短??吹綄?unlock 的調(diào)用不再需要了嗎?作為一個一般的規(guī)則,更少的代碼就是更好的代碼。因為在改變的時候這樣可以較少誤入歧途并較少產(chǎn)生誤解。
隨著資源泄露被我們甩在身后,我們可以把我們的注意力集中到數(shù)據(jù)結(jié)構(gòu)惡化。在這里我們有一個選擇,但是在我們能選擇之前,我們必須先面對定義我們的選擇的術(shù)語。 異常安全函數(shù)提供下述三種保證之一:
·函數(shù)提供基本保證(the basic guarantee),允諾如果一個異常被拋出,程序中剩下的每一件東西都處于合法狀態(tài)。沒有對象或數(shù)據(jù)結(jié)構(gòu)被破壞,而且所有的對象都處于內(nèi)部調(diào)和狀態(tài)(所有的類不變量都被滿足)。然而,程序的精確狀態(tài)可能是不可預(yù)期的。例如,我們可以重寫 changeBackground,以致于如果一個異常被拋出,PrettyMenu 對象可以繼續(xù)保留原來的背景圖像,或者它可以持有某些缺省的背景圖像,但是客戶無法預(yù)知到底是哪一個。(為了查明這一點,他們大概必須調(diào)用某個可以告訴他們當(dāng)前背景圖像是什么的成員函數(shù)。)
·函數(shù)提供強力保證(the strong guarantee),允諾如果一個異常被拋出,程序的狀態(tài)不會發(fā)生變化。調(diào)用這樣的函數(shù)在感覺上是極其微弱的,如果它們成功了,它們就完全成功,如果它們失敗了,程序的狀態(tài)就像它們從沒有被調(diào)用過一樣。
·與提供強力保證的函數(shù)一起工作比與只提供基本保證的函數(shù)一起工作更加容易,因為調(diào)用提供強力保證的函數(shù)之后,僅有兩種可能的程序狀態(tài):像預(yù)期一樣成功執(zhí)行了函數(shù),或者繼續(xù)保持函數(shù)被調(diào)用時當(dāng)時的狀態(tài)。與之相比,如果調(diào)用只提供基本保證的函數(shù)引發(fā)了異常,程序可能存在于任何合法的狀態(tài)。
假設(shè)我們有一個類,代表帶有背景圖像的 GUI 菜單。這個類被設(shè)計成在多線程環(huán)境中使用,所以它有一個用于并行控制(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 函數(shù)的可能的實現(xiàn):
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
}
從異常安全的觀點看,這個函數(shù)爛到了極點。異常安全有兩條要求,而這里全都沒有滿足。
當(dāng)一個異常被拋出,異常安全的函數(shù)應(yīng)該:
·沒有資源泄露。上面的代碼沒有通過這個測試,因為如果 "new Image(imgSrc)" 表達式產(chǎn)生一個異常,對 unlock 的調(diào)用就永遠不會執(zhí)行,而那個互斥體也將被永遠掛起。
·不允許數(shù)據(jù)結(jié)構(gòu)惡化。如果 "new Image(imgSrc)" 拋出異常,bgImage 被遺留下來指向一個被刪除對象。另外,盡管并沒有將一張新的圖像設(shè)置到位,imageChanges 也已經(jīng)被增加。(在另一方面,舊的圖像被明確地刪除,所以我料想你會爭辯說圖像已經(jīng)被“改變”了。)
規(guī)避資源泄露問題比較容易,我們以前解釋了如何使用對象管理資源,也討論了引進 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);
}
關(guān)于像 Lock 這樣的資源管理類的的事情之一是它們通常會使函數(shù)變短??吹綄?unlock 的調(diào)用不再需要了嗎?作為一個一般的規(guī)則,更少的代碼就是更好的代碼。因為在改變的時候這樣可以較少誤入歧途并較少產(chǎn)生誤解。
隨著資源泄露被我們甩在身后,我們可以把我們的注意力集中到數(shù)據(jù)結(jié)構(gòu)惡化。在這里我們有一個選擇,但是在我們能選擇之前,我們必須先面對定義我們的選擇的術(shù)語。 異常安全函數(shù)提供下述三種保證之一:
·函數(shù)提供基本保證(the basic guarantee),允諾如果一個異常被拋出,程序中剩下的每一件東西都處于合法狀態(tài)。沒有對象或數(shù)據(jù)結(jié)構(gòu)被破壞,而且所有的對象都處于內(nèi)部調(diào)和狀態(tài)(所有的類不變量都被滿足)。然而,程序的精確狀態(tài)可能是不可預(yù)期的。例如,我們可以重寫 changeBackground,以致于如果一個異常被拋出,PrettyMenu 對象可以繼續(xù)保留原來的背景圖像,或者它可以持有某些缺省的背景圖像,但是客戶無法預(yù)知到底是哪一個。(為了查明這一點,他們大概必須調(diào)用某個可以告訴他們當(dāng)前背景圖像是什么的成員函數(shù)。)
·函數(shù)提供強力保證(the strong guarantee),允諾如果一個異常被拋出,程序的狀態(tài)不會發(fā)生變化。調(diào)用這樣的函數(shù)在感覺上是極其微弱的,如果它們成功了,它們就完全成功,如果它們失敗了,程序的狀態(tài)就像它們從沒有被調(diào)用過一樣。
·與提供強力保證的函數(shù)一起工作比與只提供基本保證的函數(shù)一起工作更加容易,因為調(diào)用提供強力保證的函數(shù)之后,僅有兩種可能的程序狀態(tài):像預(yù)期一樣成功執(zhí)行了函數(shù),或者繼續(xù)保持函數(shù)被調(diào)用時當(dāng)時的狀態(tài)。與之相比,如果調(diào)用只提供基本保證的函數(shù)引發(fā)了異常,程序可能存在于任何合法的狀態(tài)。