悠悠楠杉
如何用RAII技术实现C++异常安全编程
异常安全的三个等级
在C++中,异常安全分为三个层次:
1. 基本保证:发生异常时程序保持有效状态
2. 强保证:操作要么完全成功要么保持原状态
3. 不抛保证:操作承诺不抛出异常
cpp
// 不安全示例
void riskyOperation() {
Resource* res = new Resource;
process(res); // 可能抛出异常
delete res; // 可能永远执行不到
}
RAII技术原理
Resource Acquisition Is Initialization(资源获取即初始化)的核心思想:
- 将资源生命周期与对象绑定
- 构造函数获取资源
- 析构函数释放资源
- 利用栈解退(stack unwinding)机制保证异常安全
cpp
class FileHandle {
public:
FileHandle(const char* filename) : handle(fopen(filename, "r")) {
if(!handle) throw std::runtime_error("Open failed");
}
~FileHandle() {
if(handle) fclose(handle);
}
// 禁用拷贝操作
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
private:
FILE* handle;
};
现代C++的RAII实践
- 智能指针体系
unique_ptr
:独占所有权shared_ptr
:共享所有权weak_ptr
:观察者模式
cpp
void safeOperation() {
auto ptr = std::make_unique<Resource>();
process(ptr.get()); // 即使抛出异常也能自动释放
}
- 锁守卫模式cpp
std::mutex mtx;
void threadSafeFunc() {
std::lock_guard
criticalSection();
} // 自动解锁
高级应用技巧
- Pimpl惯用法cpp
// header
class Widget {
struct Impl;
std::unique_ptrpImpl;
public:
Widget();
~Widget(); // 需显式声明
};
// cpp
struct Widget::Impl { /.../ };
Widget::Widget() : pImpl(std::make_unique
Widget::~Widget() = default; // 必须放在Impl定义之后
移动语义优化cpp
class Buffer {
char* data;
size_t size;
public:
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
}~Buffer() { delete[] data; }
};
异常安全的最佳实践
- 优先使用STL容器而非裸内存管理
- 将可能失败的初始化操作放在构造函数完成
- 对于必须实现的拷贝操作,使用copy-and-swap技术cpp
class SafeArray {
void swap(SafeArray& other) noexcept {
std::swap(data_, other.data); std::swap(size, other.size_);
}
public:
SafeArray& operator=(SafeArray other) {
swap(other);
return *this;
}
};
- 为可能抛出异常的函数明确标识
cpp void mayThrow() noexcept(false); // C++11风格
性能与安全的平衡
noexcept
标记能使编译器优化代码路径- 移动操作通常应声明为
noexcept
- 关键系统组件应考虑使用
std::terminate
替代异常
cpp
void criticalFunc() noexcept { // 不抛保证
if(errorOccurred)
std::terminate(); // 比异常传播更可控
}