C++异常
一、C++异常基础
1.异常处理机制
C++异常通过throw抛出异常对象,try/catch捕获处理:
#include <iostream>
#include <exception>
using namespace std;
int division(int dividend, int divisor) {
if (divisor == 0) {
throw std::invalid_argument("divisor must be no-zero");
}
return dividend / divisor;
}
int main() {
try {
cout << division(2, 0) << endl;
} catch (std::exception& e) {
cerr << "Error: " << e.what() << endl;
}
return 0;
}
2.异常类型
- 标准异常:继承与
std::exception(如std::runtime_error,std::logic_error,std::invalid_argument等)。 - 自定义异常:
int division(int dividend, int divisor) {
if (divisor == 0) {
throw std::invalid_argument("divisor must be no-zero");
}
return dividend / divisor;
}
二、异常安全的核心概念
异常安全的核心是确保代码在抛出异常时防止泄露资源且保持数据一致性,主要通过以下技术实现:
1.RAII(资源获取即初始化)-> 防止资源泄露
原理:通过对象的构造函数获取资源,析构函数释放资源。
代码示例:智能指针管理文件资源。
#include <cstdio>
#include <memory>
#include <iostream>
#include <exception>
#include <cstring>
struct FileDeleter {
void operator()(FILE* file) const {
if (file) {
std::fclose(file);
std::cout << "File closed." << std::endl;
}
}
};
void process_file() {
std::unique_ptr<FILE, FileDeleter> ptr(fopen("data.txt", "r"));
if (!ptr) {
throw std::runtime_error(strerror(errno));
}
/* 使用文件 */
throw std::runtime_error("Test logs for exception safety");//此处抛出异常,unique_ptr会自动调用close(file)
}
int main() {
try {
process_file();
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
编译并运行测试,结果如下:
ydqun@ydqhost chapter13 % g++-11 04-unique_ptr_for_file.cpp [0]
ydqun@ydqhost chapter13 % ./a.out [0]
File closed.
Error: Test logs for exception safety
可以看到,在抛出异常后,利用RAII,可以实现资源回收,防止资源泄露。
2.拷贝交换惯用法(Copy-and-Swap)
原理:先修改临时副本,再通过无异常操作提交变更。
代码示例:非异常安全的数组更新
template <typename T>
class UnsafeArray {
public:
/* ... */
UnsafeArray& operator=(const UnsafeArray& other) {
if (this != &other) {
data_.reset(); //先释放旧数据
data_ = make_unique<T[]>(other.size_); //可能抛出异常(如bad_alloc),此时前面释放的数据在也无法回复
copy_n(other.data_.get, other.size_, data_);
size_ = other.size_;
}
return *this;
}
/* ... */
private:
unique_ptr<T[]> data_;
size_t size_;
};
这个例程中,当在申请内存时,因内存不足而抛出异常std::bad_alloc时,会导致原数组数据丢失。正确的做法是采用拷贝交换惯用法以保证保持数据一致性,提供回滚的可能性。
代码示例:强异常安全的数组更新
template <typename T>
class SafeArray {
public:
/* ... */
SafeArray& operator=(const SafeArray& other) {
if (this != &other) { // 可选优化:避免自赋值时的冗余拷贝
auto temp = std::make_unique<T[]>(other.size_);//此处可能会抛出异常,至少可能会抛出std::bad_alloc
std::copy_n(other.data_.get(), other.size_, temp.get());//先把数据拷贝至临时对象
size_ = other.size_;//无异常操作(原子性提交)
data_.swap(temp);//使用unique_ptr的swap
}
return *this;
}
/* ... */
private:
unique_ptr<T[]> data_;
size_t size_;
};
三、异常安全的三个层次
| 安全等级 | 描述 |
|---|---|
| 基本异常安全 | 异常发生时,无资源泄露,对象保持有效状态(可能不是原始状态) |
| 强异常安全 | 异常发生时,程序状态回滚到操作前的状态(事务性操作) |
| 不抛出异常保证 | 函数承诺不抛出任何异常(如noexcept函数,析构函数默认满足此条件) |
- 1.基本保证(Basic Guarantee):
- 核心目标:防止资源泄漏并确保对象有效。
- 实现方式:通过RAII(资源获取即初始化)管理所有资源,确保即使发生异常,资源也能自动释放。
- 特点:
- 对象在异常后可能处于不同的有效状态,但不会泄漏资源。
- 例如,智能指针确保内存释放,但对象内部数据可能部分修改。
- 2.强保证(Strong Guarantee):
- 核心目标:保持数据一致性,确保操作原子性。
- 实现方式:使用事务性操作(如拷贝交换惯用法),在临时副本上执行修改,通过无异常操作(如swap)提交更改。
- 特点:
- 若操作失败,程序状态完全回滚到操作前的状态。
- 例如,文件写入临时文件后原子替换原文件,确保原文件不被破坏。
- 3.不抛异常保证(Nothrow Guarantee):
- 核心目标:确保操作不会抛出任何异常。
- 实现方式:限制操作仅使用不抛异常的代码,标记函数为noexcept。
- 特点:
- 适用于关键代码,如析构函数,避免异常传播导致程序终止。
浙公网安备 33010602011771号