RAII(資源取得即初始化,resource acquisition is initialization)
/ R-A-I-I, say it 'rah-ee' or spell the letters /
在 C 裡,呼叫 open() 後你必須記得呼叫 close();呼叫 malloc() 後你必須記得呼叫 free()。麻煩就在「記得」這兩個字:提早 return、goto,或丟出的錯誤都可能跳過清理,於是資源就洩漏了。RAII 是 C++ 的解答,名字雖然笨拙,想法卻很單純:把資源的生存期綁在某個區域物件的生存期上,當那個物件離開範圍(scope)時,清理就自動發生。
它實際上是這樣運作的。C++ 保證區域物件被銷毀時,它的解構子(destructor)一定會執行。所以你把資源包進一個小物件裡:建構子取得資源(開檔、上互斥鎖、配置記憶體),解構子釋放資源(關檔、解鎖、釋放)。從此你完全不必在呼叫處寫清理程式碼——你只要建立物件,語言就會在右大括號處替你執行解構子,無論是正常路徑、提早 return,甚至是例外正在輾轉展開(unwind)堆疊時。名字說「取得即初始化」是因為你在建構子裡抓住資源;但更重要的另一半其實是「釋放即解構」。
RAII 是現代 C++ 最重要的單一觀念,也是本欄其餘一切的地基:std::unique_ptr、std::shared_ptr、std::vector、std::lock_guard 與 std::fstream 全都是 RAII 包裝。誠實的提醒:解構子只有在物件生存期真的正常結束時才會執行——沒有對應 delete 的裸 new 不是 RAII,而強制終止行程或 std::abort() 會完全跳過所有解構子。RAII 管理的是清理由你掌控的資源,而非機器的每一種可能故障。
{ std::lock_guard<std::mutex> g(m); /* 此處 m 已上鎖 */ } // 到右大括號時 m 自動解鎖,即使區塊內丟出例外也一樣
g 離開範圍時由解構子釋放鎖——不需要明確呼叫 unlock(),例外路徑上也不會漏掉解鎖。
RAII 不是垃圾回收:清理是確定性的,發生在已知的時間點(右大括號),而非由回收器自行決定的「未來某時」。正是這份可預測性使它適合系統程式設計。