錯誤處理與穩健性

RAII 與作用域綁定的清理(RAII and scope-bound cleanup)

/ RAII: "R-A-I-I" (spelled out) /

想像一張飯店房卡,你一離開房間就自動把門鎖上——你不可能忘記鎖門,因為「離開」就是「鎖門」。RAII(資源取得即初始化,唸法是把字母逐個唸出來)就是把這個想法套用到程式裡的資源上:把資源的生命期綁到某個變數的作用域上,這樣當變數離開作用域時——以任何方式退出,包括出錯——資源就自動被釋放。你在建構子裡取得;在解構子裡釋放;當變數的作用域結束時,語言替你執行解構子。

機制如下,這是 C++/Rust 對清理問題的回應。在 C++ 裡,一個類別擁有某項資源:它的建構子取得它(開檔、配置記憶體、取鎖),它的解構子釋放它(關檔、釋放、解鎖)。當你把這樣一個物件建立為區域變數時,編譯器保證解構子會在控制流離開該作用域的那一刻執行——無論是抵達右大括號、透過 return、或是有例外向上展開穿過它。所以清理不是你要記得在每個退出處寫的東西;它依附在物件上,自動地、恰好一次地發生。Rust 取同樣的想法,並把它烤進語言裡,成為所有權與 Drop trait:當一個值的擁有者離開作用域時,它的 drop 程式碼就執行。C++ 的 std::lock_guard 和 Rust 的 MutexGuard 是教科書範例——當守衛變數的作用域結束時,鎖就被釋放,即使中途有錯誤跳出也一樣。

為何重要:RAII 大致溶解了 goto-cleanup 費力繞開的那個清理問題。沒有需要和「取得」保持同步的獨立清理標籤,也不會有「新加的提早 return 忘了釋放」的風險,因為釋放是解構子的工作,不是你的。這是在資源繁重的系統程式碼中,選 C++ 或 Rust 而非純 C 的最有力論據之一。誠實的提醒:RAII 需要 C 根本沒有的語言支援(解構子/Drop),這正是 C 退而求其次用 goto-cleanup 的原因;而 RAII 也不會神奇地杜絕每一種洩漏——參考計數指標的循環引用仍可能洩漏,你仍必須正確地設計所有權。它移除的是一整類錯誤,而非全部。

在 C++ 中:{ std::lock_guard<std::mutex> g(m); do_work(); }——鎖在 g 建立時取得,並在區塊結束的那一刻釋放,即使 do_work() 拋出例外也一樣。等價的 C 寫法需要在每一條退出路徑上明確呼叫 pthread_mutex_unlock()——這正是 goto-cleanup 之所以存在要管理的事。

守衛的解構子在每個退出處自動解鎖;C 版本則必須親手做。

RAII 需要解構子或 Drop——這是 C 所沒有的特性,這也正是 C 改用 goto-cleanup 的原因。而它也不是防洩漏的保證:循環引用仍可能洩漏,糟糕的所有權設計仍會反咬。它移除的是一類錯誤,而非全部。

又称
resource acquisition is initializationscope-bound resource management資源取得即初始化