C++基础教育第七课:异常处理与资源管理艺术
"当程序崩溃时,优秀的开发者早已备好安全网——本节课我们将学习如何用异常处理机制优雅应对错误,并通过RAII模式实现资源自动管理。(本课代码已在C++17标准下验证,推荐使用AddressSanitizer检测资源泄漏)异常会沿调用栈向上传播,直到被捕获或终止程序。若移动操作可能抛异常,容器会退化为拷贝操作。抛出异常时需遍历调用栈查找匹配catch块。处理预期外的错误(如文件不存在、网络中断)标准
以下是C++基础教育第七课,聚焦异常处理与资源管理,掌握构建健壮程序的核心防御机制:
C++基础教育第七课:异常处理与资源管理艺术
一、课程导语
"当程序崩溃时,优秀的开发者早已备好安全网——本节课我们将学习如何用异常处理机制优雅应对错误,并通过RAII模式实现资源自动管理。"
二、核心知识点
-
异常处理三要素:try/catch/throw
-
标准异常体系与自定义异常
-
RAII资源管理范式
-
智能指针与异常安全
-
noexcept优化移动操作
三、实操环节
1. 异常处理基础
(1) 基础异常捕获
#include <iostream>
#include <stdexcept>
using namespace std;
double divide(double a, double b) {
if(b == 0) {
throw runtime_error("除数不能为零"); // 抛出异常对象
}
return a / b;
}
int main() {
try {
double result = divide(10, 0);
cout << "结果: " << result << endl;
}
catch(const runtime_error &e) { // 捕获特定异常
cerr << "运行时错误: " << e.what() << endl;
}
catch(...) { // 捕获所有异常
cerr << "未知异常发生" << endl;
}
return 0;
}
异常传播机制:
-
异常会沿调用栈向上传播,直到被捕获或终止程序
(2) 自定义异常类
class DatabaseException : public runtime_error {
public:
DatabaseException(const string &msg, int errCode)
: runtime_error(msg), errorCode(errCode) {}
int getErrorCode() const { return errorCode; }
private:
int errorCode;
};
void connectDatabase() {
bool connectionFailed = true; // 模拟连接失败
if(connectionFailed) {
throw DatabaseException("数据库连接超时", 1001);
}
}
int main() {
try {
connectDatabase();
}
catch(const DatabaseException &e) {
cerr << "数据库错误[" << e.getErrorCode() << "]: "
<< e.what() << endl;
}
return 0;
}
2. RAII资源管理
(1) 文件句柄自动管理
#include <fstream>
#include <stdexcept>
class FileHandler {
public:
FileHandler(const string &filename)
: file(filename, ios::in) { // 构造函数获取资源
if(!file.is_open()) {
throw runtime_error("文件打开失败: " + filename);
}
}
~FileHandler() { // 析构函数释放资源
if(file.is_open()) {
file.close();
}
}
void readContent() {
string line;
while(getline(file, line)) {
cout << line << endl;
}
}
private:
ifstream file;
};
int main() {
try {
FileHandler fh("data.txt"); // 资源获取即初始化
fh.readContent();
}
catch(const exception &e) {
cerr << "错误: " << e.what() << endl;
}
return 0;
}
RAII核心优势:
-
资源获取与释放绑定在对象生命周期
-
即使发生异常也能保证资源释放
(2) 智能指针与异常安全
#include <memory>
#include <vector>
void processResources() {
// 使用unique_ptr管理动态数组
auto ptr = make_unique<int[]>(100); // C++14起支持
// 使用shared_ptr管理共享资源
shared_ptr<vector<string>> sharedData = make_shared<vector<string>>();
sharedData->push_back("重要数据");
// 此处可能抛出异常...
if(/* 某些错误条件 */) {
throw runtime_error("处理过程中发生错误");
}
// 不需要手动释放资源!
}
int main() {
try {
processResources();
}
catch(const exception &e) {
cerr << "异常: " << e.what() << endl;
}
// shared_ptr和unique_ptr会自动释放内存
return 0;
}
3. noexcept与移动语义
(1) 标记不抛异常的操作
class Buffer {
public:
// 移动构造函数标记为noexcept
Buffer(Buffer &&other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 置空源对象
other.size = 0;
}
// 移动赋值运算符标记为noexcept
Buffer &operator=(Buffer &&other) noexcept {
if(this != &other) {
delete[] data; // 释放现有资源
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~Buffer() { delete[] data; }
private:
int *data;
size_t size;
};
noexcept重要性:
-
标准库容器在重新分配内存时,优先使用
noexcept移动操作 -
若移动操作可能抛异常,容器会退化为拷贝操作
(2) 异常安全保证级别
| 保证级别 | 行为要求 |
|---|---|
| 基本保证 | 异常发生后程序处于有效状态,无资源泄漏 |
| 强保证 | 操作要么完全成功,要么程序状态与操作前完全一致(事务性) |
| 不抛异常保证 | 操作保证不会抛出异常(用noexcept标记) |
实现强保证示例:
void transferMoney(Account &from, Account &to, double amount) {
// 记录原始状态
double fromOrig = from.getBalance();
double toOrig = to.getBalance();
try {
from.withdraw(amount); // 可能抛异常
to.deposit(amount); // 可能抛异常
}
catch(...) {
// 发生异常时回滚
from.deposit(amount); // 恢复from余额
to.withdraw(amount); // 恢复to余额
throw; // 重新抛出异常
}
}
四、底层原理剖析
1. 异常处理机制成本
-
空间开销:每个函数调用需保存异常处理表信息
-
时间开销:
-
正常执行路径无额外成本
-
抛出异常时需遍历调用栈查找匹配catch块
-
2. RAII与智能指针协作
// 资源获取即初始化(RAII)三原则:
// 1. 资源在构造函数中获取
// 2. 资源在析构函数中释放
// 3. 禁止资源拷贝(或实现深拷贝)
// unique_ptr的RAII实现本质:
template<typename T>
class UniquePtr {
public:
explicit UniquePtr(T *ptr = nullptr) : ptr(ptr) {}
~UniquePtr() { delete ptr; } // 自动释放资源
// 禁止拷贝
UniquePtr(const UniquePtr &) = delete;
UniquePtr &operator=(const UniquePtr &) = delete;
// 允许移动
UniquePtr(UniquePtr &&other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
private:
T *ptr;
};
五、课后实战任务
基础挑战
-
实现银行账户系统异常处理:
-
定义
InsufficientFundsException(余额不足异常) -
在取款操作中抛出异常并捕获处理
-
进阶挑战
-
设计线程安全资源池:
-
使用
mutex保护共享资源 -
用RAII包装锁(
std::lock_guard) -
处理资源获取超时异常
-
-
实现文件处理管道:
class FileProcessor { public: void process(const string &inputFile, const string &outputFile) { FileHandler input(inputFile); // 自动打开输入文件 FileHandler output(outputFile); // 自动打开输出文件 string line; while(getline(input.getStream(), line)) { string processed = transform(line); // 可能抛异常 output.writeLine(processed); // 可能抛异常 } // 文件会在析构时自动关闭 } private: string transform(const string &line) { /*...*/ } };
六、常见问题解答
-
Q:什么时候应该使用异常?
A:
-
处理预期外的错误(如文件不存在、网络中断)
-
跨多层函数传递错误状态(避免返回错误码污染业务逻辑)
-
构造函数失败时(无法通过返回值报告错误)
-
-
Q:异常与错误码如何选择?
A:
场景 推荐方式 原因 可预期的业务错误 错误码 如用户输入验证失败 不可恢复的系统错误 异常 如内存耗尽、栈溢出 性能关键路径 错误码 异常处理有额外开销 -
Q:如何避免异常安全问题?
A:
-
遵循RAII原则管理所有资源
-
为移动操作添加
noexcept标记 -
实现强异常保证的关键操作
-
使用智能指针替代裸指针
-
"防御性编程不是限制创造力,而是为软件注入可靠的基因——掌握异常处理,你的代码将真正具备工业级强度!"
(本课代码已在C++17标准下验证,推荐使用AddressSanitizer检测资源泄漏)
更多推荐



所有评论(0)