4.2. RAII 原则
4.2.1. 什么是 RAII
RAII (Resource Acquisition Is Initialization) 是 C++ 最重要的编程范式之一:
获取资源时初始化对象
释放资源时销毁对象
// 经典 RAII 示例:文件句柄
class File {
FILE* handle_;
public:
File(const char* path, const char* mode)
: handle_(fopen(path, mode)) {
if (!handle_) throw std::runtime_error("Cannot open file");
}
~File() {
if (handle_) fclose(handle_);
}
// 禁止拷贝
File(const File&) = delete;
File& operator=(const File&) = delete;
// 允许移动
File(File&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
FILE* get() const { return handle_; }
};
void process_file() {
File f("data.txt", "r"); // 获取资源
// 使用文件...
} // 自动释放,即使发生异常
4.2.2. RAII 的优势
4.2.2.1. 1. 异常安全
// 没有 RAII:需要手动清理
void dangerous() {
int* data = new int[100];
if (error_condition) {
delete[] data; // 容易遗忘
return;
}
process(data);
delete[] data;
}
// 使用 RAII:自动清理
void safe() {
auto data = std::make_unique<int[]>(100);
if (error_condition) {
return; // 自动释放
}
process(data.get());
} // 自动释放
4.2.2.2. 2. 代码简洁
// 没有 RAII:繁琐的清理代码
void messy() {
Resource* r1 = acquire_r1();
if (!r1) return;
Resource* r2 = acquire_r2();
if (!r2) {
release_r1(r1);
return;
}
Resource* r3 = acquire_r3();
if (!r3) {
release_r2(r2);
release_r1(r1);
return;
}
// 使用资源...
release_r3(r3);
release_r2(r2);
release_r1(r1);
}
// 使用 RAII:简洁清晰
void clean() {
auto r1 = RAII_Resource1();
auto r2 = RAII_Resource2();
auto r3 = RAII_Resource3();
// 使用资源...
} // 自动按逆序释放
4.2.3. 常见 RAII 类
4.2.3.1. 标准库 RAII 类
// 智能指针
std::unique_ptr<T>
std::shared_ptr<T>
// 文件流
std::ifstream, std::ofstream, std::fstream
// 锁
std::lock_guard<std::mutex>
std::unique_lock<std::mutex>
std::scoped_lock<>
// 其他
std::string // 管理字符数组
std::vector<T> // 管理动态数组
4.2.3.2. 自定义 RAII 类
// 互斥锁守卫
class MutexGuard {
std::mutex& mutex_;
public:
explicit MutexGuard(std::mutex& m) : mutex_(m) {
mutex_.lock();
}
~MutexGuard() {
mutex_.unlock();
}
MutexGuard(const MutexGuard&) = delete;
MutexGuard& operator=(const MutexGuard&) = delete;
};
// 数据库事务
class Transaction {
Database& db_;
bool committed_ = false;
public:
explicit Transaction(Database& db) : db_(db) {
db_.begin_transaction();
}
~Transaction() {
if (!committed_) {
db_.rollback(); // 未提交则回滚
}
}
void commit() {
db_.commit();
committed_ = true;
}
};
void update_data(Database& db) {
Transaction txn(db);
db.execute("UPDATE ...");
db.execute("INSERT ...");
txn.commit(); // 成功则提交
} // 异常时自动回滚
4.2.4. Scope Guard
通用的 RAII 辅助类,在作用域结束时执行指定操作。
template<typename F>
class ScopeGuard {
F func_;
bool active_;
public:
explicit ScopeGuard(F f) : func_(std::move(f)), active_(true) {}
~ScopeGuard() {
if (active_) func_();
}
void dismiss() { active_ = false; }
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& other) noexcept
: func_(std::move(other.func_)), active_(other.active_) {
other.active_ = false;
}
};
template<typename F>
ScopeGuard<F> make_scope_guard(F f) {
return ScopeGuard<F>(std::move(f));
}
// 使用
void example() {
auto* resource = acquire_resource();
auto guard = make_scope_guard([&]{ release_resource(resource); });
// 使用资源...
if (success) {
guard.dismiss(); // 成功时不释放
}
} // 失败时自动释放
4.2.4.1. C++20 defer 宏风格
#define CONCAT_IMPL(x, y) x##y
#define CONCAT(x, y) CONCAT_IMPL(x, y)
#define defer ScopeGuard CONCAT(_defer_, __LINE__) = [&]()
void example() {
FILE* f = fopen("file.txt", "r");
defer { fclose(f); };
// 使用文件...
} // 自动关闭
4.2.5. RAII 与异常
4.2.5.1. 异常安全等级
// 基本保证:发生异常后,对象仍处于有效状态
class BasicGuarantee {
std::vector<int> data_;
public:
void add(int x) {
data_.push_back(x); // 可能抛异常
// 但 data_ 仍然有效
}
};
// 强保证:发生异常后,状态回滚到操作前
class StrongGuarantee {
std::vector<int> data_;
public:
void replace(const std::vector<int>& new_data) {
auto copy = new_data; // 可能抛异常
std::swap(data_, copy); // noexcept
}
};
// 不抛保证:操作永不抛异常
class NoThrowGuarantee {
int value_;
public:
int get() const noexcept { return value_; }
void set(int v) noexcept { value_ = v; }
};
4.2.5.2. 析构函数不应抛异常
class Bad {
public:
~Bad() {
throw std::runtime_error("error"); // 非常危险!
}
};
class Good {
public:
~Good() noexcept {
try {
cleanup();
} catch (...) {
// 记录日志但不传播
}
}
};
4.2.6. RAII 最佳实践
4.2.6.1. 1. 构造时获取,析构时释放
class Resource {
void* handle_;
public:
Resource() : handle_(acquire()) {
if (!handle_) throw std::runtime_error("Failed to acquire");
}
~Resource() {
if (handle_) release(handle_);
}
};
4.2.6.2. 2. 遵循 Rule of Zero/Five
// Rule of Zero:优先使用现有 RAII 类
class ModernClass {
std::string name_;
std::unique_ptr<Resource> resource_;
// 不需要自定义析构函数
};
// Rule of Five:如果需要自定义析构,考虑所有五个
class CustomClass {
public:
CustomClass();
~CustomClass();
CustomClass(const CustomClass&);
CustomClass& operator=(const CustomClass&);
CustomClass(CustomClass&&) noexcept;
CustomClass& operator=(CustomClass&&) noexcept;
};
4.2.6.3. 3. 使用 noexcept
class SafeClass {
public:
~SafeClass() noexcept; // 析构函数应该是 noexcept
SafeClass(SafeClass&&) noexcept; // 移动操作应该是 noexcept
SafeClass& operator=(SafeClass&&) noexcept;
};
小技巧
RAII 核心思想:
让编译器帮你管理资源生命周期
构造时获取,析构时释放
析构函数不抛异常
优先使用标准库的 RAII 类