C++箴言:爭(zhēng)取異常安全的代碼

字號(hào):

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