條款 13:以對象管理資源
核心思想:將資源封裝在對象內,利用 C++ 析構函數自動調用機制 確保資源釋放。這就是著名的 RAII(Resource Acquisition Is Initialization,資源獲取即初始化) 慣用語。
問題背景
許多資源(動態內存、文件句柄、鎖、Socket、數據庫連接)在堆上分配,用於單一作用域/函數。它們必須在控制流離開該作用域時被釋放。手動管理極易出錯:
- 忘記釋放 → 資源洩漏
- 提前返回/異常拋出 → 釋放代碼被跳過
- 重複釋放 → 未定義行為
標準庫方案:auto_ptr (C++98) / unique_ptr (C++11)
auto_ptr 示例
class Investment { ... };
Investment* createInvestment(); // 返回動態分配對象,調用者負責刪除
void f() {
std::auto_ptr<Investment> pInv(createInvestment());
// 使用 pInv...
} // pInv 析構時自動 delete 所指對象,無洩漏風險
auto_ptr 關鍵特性(及缺陷)
- 構造時接管所有權
- 析構時
delete 對象
- 拷貝/賦值會轉移所有權:源
auto_ptr 置空,目標接管
- 缺陷:
std::vector<auto_ptr> 非法、std::sort 失效、不可用於數組
更優方案:引用計數智能指針
std::tr1::shared_ptr (C++11 std::shared_ptr) 共享所有權,最後一個銷毀時釋放:
void f() {
std::tr1::shared_ptr<Investment> pInv1(createInvestment());
std::tr1::shared_ptr<Investment> pInv2(pInv1); // 共享
pInv1 = pInv2; // 賦值共享
// 離開作用域時,最後一個 shared_ptr 釋放對象
}
兩個關鍵設計原則
- 資源獲取即初始化:在構造函數中獲取資源(或接管裸指針)
- 析構函數釋放資源:析構函數自動調用,無視返回路徑(正常/異常/提前 return)
常用 RAII 類
| 資源類型 | RAII 封裝 | 標準庫 |
| 動態內存 | unique_ptr / shared_ptr | <memory> |
| 互斥鎖 | lock_guard / unique_lock | <mutex> |
| 文件 | fstream / FILE* 封裝 | <fstream> |
| Socket | 自定義 SocketGuard | - |
| 數據庫連接 | 自定義 ConnectionGuard | - |
記住
為防止資源洩漏,請使用 RAII 對象:構造函數獲取資源,析構函數釋放資源。
兩個常用 RAII 類:std::tr1::shared_ptr 和 auto_ptr。前者優於後者,因為拷貝行為直觀(共享而非轉移)。若選 auto_ptr,拷貝會使源對象置空。
現代 C++ 最佳實踐 (C++11/14/17/20)
- 獨占所有權:
std::unique_ptr (配合 std::make_unique C++14)
- 共享所有權:
std::shared_ptr (配合 std::make_shared)
- 弱引用/觀察者:
std::weak_ptr
- 鎖管理:
std::lock_guard (簡單)、std::unique_lock (靈活)
- 自定義資源:封裝裸指針/句柄到 RAII 類,指定自定義刪除器
// 自定義 RAII 範例:管理 C 風格資源
class FileGuard {
FILE* fp_;
public:
explicit FileGuard(const char* path, const char* mode) : fp_(fopen(path, mode)) {
if (!fp_) throw std::runtime_error("fopen failed");
}
~FileGuard() { if (fp_) fclose(fp_); }
// 禁止拷貝,允許移動
FileGuard(const FileGuard&) = delete;
FileGuard& operator=(const FileGuard&) = delete;
FileGuard(FileGuard&& other) noexcept : fp_(other.fp_) { other.fp_ = nullptr; }
FileGuard& operator=(FileGuard&& other) noexcept {
if (this != &other) { if (fp_) fclose(fp_); fp_ = other.fp_; other.fp_ = nullptr; }
return *this;
}
FILE* get() const { return fp_; }
};
// 使用
void writeLog() {
FileGuard fg("app.log", "a");
fprintf(fg.get(), "Log entry\n");
} // 自動 fclose
- 上一篇文章: C++11 shared_ptr 完全指南:共享所有權、控制塊、make_shared、線程安全與避坑指南
- 下一篇文章: C++ 函數對象實戰:解決資源釋放策略多態的最佳模式
Copyright ©
程式碼之家 All Rights Reserved