在 C++ 中,auto_ptr(已棄用)與 std::shared_ptr 等智慧指標專門管理堆積記憶體資源。然而,現實世界中並非所有資源都來自堆積,例如檔案控制代碼、互斥鎖、網路通訊端、資料庫連線等。這時我們需要自行設計資源管理類別,而 RAII(Resource Acquisition Is Initialization) 是此類設計的基石:資源在建構函式中獲取,在解構函式中釋放,確保不會發生資源洩漏。
Lock假設我們使用 C 風格 API 操作互斥鎖:
void lock(Mutex* pm); // 鎖定 pm 指向的互斥鎖
void unlock(Mutex* pm); // 解除鎖定
為了確保鎖一定會被釋放,我們設計一個 RAII 風格的 Lock 類別:
class Lock {
public:
explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr); } // 獲取資源
~Lock() { unlock(mutexPtr); } // 釋放資源
private:
Mutex* mutexPtr;
};
使用方式符合 RAII 慣例:
Mutex m;
{
Lock ml(&m); // 鎖定互斥鎖,進入臨界區
// ... 執行臨界區操作
} // 區塊結束,ml 解構,自動解除鎖定
當 Lock 物件被複製時,會發生什麼事?
Lock ml1(&m); // 鎖定 m
Lock ml2(ml1); // 複製 ml1 到 ml2,這該怎麼辦?
資源管理類別的複製行為取決於其所管理資源的複製特性。主要有四種策略:
private(C++98)或標記為 = delete(C++11)。這是 std::unique_ptr、std::lock_guard 的做法,適用於獨占所有權的資源。std::shared_ptr 的核心機制,適用於共享所有權的場景。std::auto_ptr(已棄用)的行為,現代 C++ 推薦使用移動語意(std::move)來明確表達所有權轉移。複製 RAII 物件必須一並複製其所管理的資源,因此資源的複製行為決定了 RAII 物件的複製行為。
最普遍、最常見的 RAII 類別複製行為是:抑制複製(如 std::unique_ptr、std::lock_guard)與 引用計數(如 std::shared_ptr)。但根據資源特性,其他行為也可能是合理的實現選項。設計時務必明確資源的所有權語意,並據此選擇或組合上述策略。