系統程式設計師的 C++

noexcept 與例外安全(noexcept and exception safety)

C++ 有兩種主要方式回報某個操作失敗了。一種是 C 風格的做法,回傳錯誤碼並信任呼叫者去檢查它;另一種是丟出例外,它立刻輾轉展開(unwind)堆疊——沿途執行解構子——直到找到相符的 catch 處理常式。例外讓成功路徑保持乾淨,並使人難以默默忽略錯誤,但它引出一個棘手的問題:當例外在操作進行到一半時穿過你的程式碼,你的物件被留在什麼狀態?那個問題正是例外安全的主題,而 noexcept 是用來承諾某個操作根本不會丟例外的關鍵字。

例外安全通常被描述為函式能提供的一道保證階梯。基本保證(basic guarantee):若例外逸出,沒有資源洩漏,所有物件維持在某個有效(即便未指定)的狀態。強保證(strong guarantee):若例外逸出,操作完全沒有效果——就像你從未呼叫過它(提交或回滾的性質;vector 操作常藉由在副本上工作、最後再交換來達成)。不丟例外保證(nothrow guarantee):操作絕不丟例外,就這樣。noexcept 指定符把函式標記為不丟例外;它既是文件也是編譯器可倚賴的契約——而若一個 noexcept 函式竟然丟了例外,程式會立即經由 std::terminate 被終止,不做任何展開。

為何重要:noexcept 不只是承諾,它還是個促成者。標準函式庫會檢視它——最重要的是,std::vector 只有在元素的移動建構子是 noexcept 時,才會在重新配置時移動它的元素;否則它退回去使用複製,以保住強保證(移動到一半丟出例外可能讓元素半轉移而無法復原)。所以把你的移動操作標記為 noexcept,可以讓你的型別在容器裡快上許多。誠實的提醒:例外在不丟出的路徑上大致是零成本,但真正丟出時昂貴,這正是為什麼許多延遲關鍵與嵌入式程式碼停用它們、改用錯誤碼;而錯誤的 noexcept(對可能丟例外之物宣稱不丟)不會讓它變安全——它讓一個丟出的例外變成硬性崩潰。

Buffer(Buffer&& o) noexcept : data(o.data) { o.data = nullptr; } // 把移動標記為 noexcept,使 std::vector 在成長時移動(而非複製)Buffer

noexcept 的移動建構子正是讓提供強保證的容器得以用移動(而非較安全但較慢的複製)來搬遷的關鍵。

用 noexcept 撒謊很危險:若一個宣告為 noexcept 的函式竟然丟出例外,執行期會呼叫 std::terminate,程式就此死亡——沒有復原餘地。noexcept 是個編譯器以「把你殺掉」來強制執行的承諾,而非提示,所以只在你確定沒有任何東西會丟例外的地方才套用它。

又稱
exception guaranteeserror handling models例外保證例外安全等級