從Gury of the Week條款8的最初發布到現在,這一系列的條款已經走過了很長的一段路程。我希望你能喜歡它們並且發現它們確實很有用。我要特別感謝委員會成員Dave Abrahams和Greg Colvin,感謝他們在闡述異常安全性上的深刻洞察力,以及對這部分內容的草稿所提出的中肯批評。Dave和Grep,以及Matt Austern,他們一起編寫了兩個完整的會議提案,這些提案的議題是將當前的異常安全性保證添加到標準庫中。
在這個系列的條款中,我們將討論兩個主題:首先是C++的兩個主要特性,異常處理和範本,我們將分析如何編寫異常安全的代碼(即在出現異常的情況下代碼仍能正確運行);其次是異常中立(即將所有的異常都轉發給調用者)的泛型容器。這些東西說起來很容易,但做起來絕不輕松。
現在,我們一起來實現一個簡單的容器(一個能執行壓入操作和彈出操作的棧),並分析如果要使這個容器成為異常安全的和異常中立的,我們需要解決哪些問題。
我們將從Cargill停下來的地方開始——也就是,逐步地創建一個異常安全的範本stack,而cargill當初也正是以Stack為例來提出他的問題的。稍後,我們將降低對範本參數類型T的需求,顯著改善Stack容器,然後還將給出一些高級技巧,用於在管理資源時實現異常安全性。按照這種方法,我們可以找出下列問題的答案:
異常安全性的不同“級別”指的是什麼?
泛型容器可以是或者說應該是完全異常中立的嗎?
標準庫中的容器類是異常安全的還是異常中立的?
異常安全性會不會影響對容器公共介面的設計?
在泛型容器中應該使用異常規範嗎?
這個構造函數是異常安全的和異常中立的嗎?要搞清楚這個問題,我們先考慮在哪些地方將可能拋出異常。簡單來說,答案就是:在每個函數中。因此,第一步就是對上述代碼進行分析並確定實際調用了哪些函數,包括自由函數、構造函數、析構函數、運算符重載函數,以及其他的成員函數。
在Stack的構造函數中,首先將vsize_設為10,然後通過new T(vsize_)來分配初始內存。後者將首先調用operator new()()(或者是默認的operator new(),或者是由T提供的operator new())來分配內存,然後再調用vsize_次T::T函數。在這個過程中有兩個操作可能會失敗。第一個操作是內存分配操作本身,在失敗的情況下。perator new()()將拋出一個bad alloc異常。第二個操作是T的默認構造函數,在這個函數中可能拋出任意的異常,在這種情況下,所有已經構造的T對象都會被銷毀,並且通過調用。perator delete()()來確保已分配的內存被自動回收。
因此,上面的函數是完全異常安全的和異常中立的,我們繼續來看下一個問題……什麼?你問為什麼這個函數是健壯的?好吧,我們對這個函數進行更為詳細的討論。
1.這個函數是異常中立的。在函數中不會捕獲任何異常,因此,如果new拋出了異常,那麼這個異常就會像我們所要求的那樣被正確地轉發給調用者。