C++ 异常处理
1. 传统错误处理 vs C++异常处理
传统方法:返回值表示错误
int divide(int a, int b, int& result) {
if (b == 0) return -1; // 错误码
result = a / b;
return 0; // 成功
}
int main() {
int res;
if (divide(10, 0, res) != 0) {
std::cerr << "Division by zero!" << std::endl;
}
}
- 问题:错误码可能被忽略,且返回值被占用(无法同时返回结果和状态)。
- 优点:简单直观,但难以处理多层嵌套调用的错误。
C++异常处理:try/catch/throw
int divide(int a, int b) {
if (b == 0) throw std::runtime_error("Division by zero!");
return a / b;
}
int main() {
try {
int res = divide(10, 0);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
- 优势:错误处理与正常逻辑分离,支持跨多层函数调用传递错误。
2. 异常触发与栈展开
栈展开(Stack Unwinding)
当异常抛出时:
- 当前函数停止执行,局部对象按构造逆序析构。
- 系统沿调用栈向上查找匹配的
catch块。 - 若未找到,程序终止(调用
std::terminate)。
struct Logger {
Logger() { std::cout << "Logger created\n"; }
~Logger() { std::cout << "Logger destroyed\n"; }
};
void funcB() {
Logger log;
throw std::runtime_error("Error in funcB");
}
void funcA() {
Logger log;
funcB();
}
int main() {
try {
funcA();
} catch (...) {
std::cout << "Exception caught\n";
}
}
输出:
Logger created (funcA)
Logger created (funcB)
Logger destroyed (funcB) <- 栈展开开始
Logger destroyed (funcA)
Exception caught
3. 异常对象
- 抛出异常时,系统用异常表达式拷贝初始化一个临时对象(称为异常对象)。
- 异常对象在栈展开期间存活,直到
catch块结束。
示例:引用捕获避免拷贝
try {
throw std::runtime_error("Error");
} catch (const std::exception& e) { // 引用捕获,避免拷贝
std::cerr << e.what();
}
4. try/catch语句块
基本语法与匹配顺序
try {
// 可能抛出异常的代码
} catch (const std::runtime_error& e) {
// 处理特定异常
} catch (const std::exception& e) {
// 处理基类异常
} catch (...) {
// 处理所有其他异常
}
- 匹配顺序:从上到下,第一个匹配的
catch块被执行。 catch(...):捕获任意异常,通常用于最后兜底。
重新抛出异常
try {
throw std::runtime_error("Original error");
} catch (...) {
std::cout << "Logging error...\n";
throw; // 重新抛出原异常
}
5. 异常与构造/析构函数
构造函数中的异常
- 若构造函数抛出异常:
- 已构造的成员和基类子对象会被析构。
- 当前对象的析构函数不会被调用。
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
};
class Widget {
Resource res;
public:
Widget() {
throw std::runtime_error("Widget failed");
}
~Widget() { /* 不会执行 */ }
};
int main() {
try {
Widget w;
} catch (...) {
std::cout << "Exception caught\n";
}
}
输出:
Resource acquired
Resource released // 成员res被析构
Exception caught
析构函数中的异常
- 危险行为:析构函数中抛出异常可能导致程序终止。
class Danger {
public:
~Danger() noexcept(false) {
throw std::runtime_error("Oops");
}
};
int main() {
try {
Danger d;
} catch (...) {
// 析构函数抛出的异常可能未被捕获
}
} // 若异常未被捕获,调用std::terminate
6. noexcept关键字
声明函数不抛异常
void safeFunc() noexcept {
// 保证不抛异常
}
void riskyFunc() noexcept(false) {
// 可能抛异常
}
noexcept操作符
void funcA() noexcept {}
void funcB() { throw 42; }
static_assert(noexcept(funcA())); //noexcept(funcA()) true
static_assert(!noexcept(funcB())); //noexcept(funcB()) false
noexcept与性能
- 标记
noexcept的函数允许编译器优化(如省略栈展开代码)。 - 违反
noexcept声明会导致std::terminate。
void terminateIfThrows() noexcept {
throw 42; // 程序终止
}
总结与最佳实践
- 优先使用异常处理错误,而非返回值。
- 析构函数禁止抛异常(标记为
noexcept)。 - 按引用捕获异常(避免对象切片和拷贝)。
- 合理使用
noexcept,优化性能并明确接口契约。 - 构造函数失败时抛异常,避免返回半构造对象。
通过结合RAII(资源获取即初始化)模式,可确保异常安全:
class FileHandler {
FILE* file;
public:
FileHandler(const char* path) : file(fopen(path, "r")) {
if (!file) throw std::runtime_error("File open failed");
}
~FileHandler() { if (file) fclose(file); }
};

被折叠的 条评论
为什么被折叠?



