RAII(Resource Acquisition Is Initialization)是 C++ 管理資源、避免洩漏的核心慣用法。本文系統闡述 RAII 定義、必要性、標準實作模式、鎖管理實戰範例、複製建構陷阱根因分析與現代 C++ 最佳實踐。
利用 C++ 物件生命週期結束自動呼叫解構函數特性:建構時獲取資源、生命期內管控存取、解構時釋放資源。
系統資源(記憶體、鎖、檔案句柄、Socket、GPU 顯存)有限,使用必須遵循:**申請 → 使用 → 釋放**。手動管理易發生:
if (!OpA()) { delete p; return; } 重複程式碼)。class ResourceWrapper {
Resource *m_res;
public:
ResourceWrapper() : m_res(AcquireResource()) {} // 獲取
~ResourceWrapper() { ReleaseResource(m_res); } // 釋放(自動、例外安全)
// 禁用複製/移動或正确实现深拷貝/移動語意
ResourceWrapper(const ResourceWrapper&) = delete;
ResourceWrapper& operator=(const ResourceWrapper&) = delete;
ResourceWrapper(ResourceWrapper&& other) noexcept : m_res(other.m_res) { other.m_res = nullptr; }
ResourceWrapper& operator=(ResourceWrapper&& other) noexcept { /* ... */ }
Resource* get() { return m_res; }
};
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
CRITICAL_SECTION g_cs;
int g_global = 0;
class ScopedLock {
public:
ScopedLock() { EnterCriticalSection(&g_cs); }
~ScopedLock() { LeaveCriticalSection(&g_cs); }
// 關鍵:禁止複製,避免雙重解鎖/提前解鎖
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
void DoComplex(ScopedLock&) {} // 傳參考,不複製
unsigned __stdcall ThreadFunc(void *p) {
ScopedLock lock; // 進入鎖
DoComplex(lock);
for (int i = 0; i < 10; ++i) {
++g_global;
cout << "Thread " << *(int*)p << ": " << g_global << endl;
}
return 0; // 離開作用域,自動解鎖
}
int main() {
InitializeCriticalSection(&g_cs);
int t1 = 1, t2 = 2;
HANDLE h[2] = { (HANDLE)_beginthreadex(nullptr, 0, ThreadFunc, &t1, 0, nullptr),
(HANDLE)_beginthreadex(nullptr, 0, ThreadFunc, &t2, 0, nullptr) };
WaitForMultipleObjects(2, h, TRUE, INFINITE);
DeleteCriticalSection(&g_cs);
}
若 ScopedLock 允許複製(預設淺拷貝),呼叫 DoComplex(lock) 時會觸發複製建構,產生臨時物件。臨時物件離開 DoComplex 作用域時解構 → 呼叫 LeaveCriticalSection → **提前釋放鎖**,導致後續程式碼無鎖保護,資料競爭發生。
| 方案 | 做法 | 優缺點 |
|---|---|---|
| 刪除複製建構/賦值 | = delete (C++11) 或私有宣告未定義 (C++98) | 編譯期阻止錯誤用法,最推薦 |
| 實作參考計數複製 | 複製時 EnterCriticalSection,解構時 Leave(需可重入鎖) | 複雜、僅適用可重入鎖,不推薦 |
| 傳參考/指標 | void Func(ScopedLock&) | 避免複製,但無法強制呼叫端遵守 |
std::lock_guard<Mutex> / std::unique_lock<Mutex>:鎖管理。std::unique_ptr<T, Deleter> / std::shared_ptr<T>:記憶體/資源管理。std::fstream、std::filebuf:檔案句柄。std::vector/string/span:動態陣列/字串。RAII 核心:**擁有物件即擁有資源,物件在資源就在**。善用 RAII 寫出的程式碼簡潔、優雅、例外安全。現代 C++ 開發應將 RAII 內化為肌肉記憶,拒絕裸指標與手動釋放。