1、C++中,class的析构函数不应该抛出异常主要原因有:
1.1、 栈展开(Stack Unwinding)问题
当异常抛出时,C++会进行栈展开,调用局部对象的析构函数。如果析构函数本身抛出异常,程序将面临两个异常同时存在的情况,这会触发std::terminate,导致程序终止。
当C++程序抛出异常时,会启动栈展开过程。栈展开的目的是销毁当前作用域内的局部对象,并调用它们的析构函数,以确保资源被正确释放。
如果在栈展开过程中,某个析构函数又抛出了一个新的异常,那么此时会有两个异常同时存在:
- 一个是正在处理的原始异常;
- 另一个是析构函数中抛出的新异常。
C++标准规定,不允许同时存在两个活跃的异常。因此,程序会调用 std::terminate,直接终止程序。
- 例子:程序会调用std::terminate,导致崩溃,而不是捕获异常
#include <iostream>
#include <stdexcept>
class MyClass {
public:
~MyClass() noexcept(false) {
// 析构函数中抛出异常
throw std::runtime_error("Exception in destructor");
}
};
int main() {
try {
MyClass obj;
// obj 离开作用域时,析构函数会被调用
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
1.2、资源泄漏风险
如果析构函数抛出异常,可能导致资源未正确释放,因为异常会中断析构函数的正常执行流程。
1.3、违反异常安全原则
析构函数应确保资源释放,抛出异常会破坏这一原则,增加程序的不确定性。
2、处理方式
2.1、捕获并处理异常
- 在析构函数中捕获可能抛出的异常,确保异常不会传播到析构函数外部
- 将函数标记为noexcept,确保不会抛出异常,如果异常未捕获,程序会调用std::terminate
class MyClass {
public:
~MyClass() noexcept {
try {
// 可能抛出异常的代码
someMethodThatMayThrow();
} catch (const std::exception& e) {
// 记录异常或处理异常
std::cerr << "Exception in destructor: " << e.what() << std::endl;
} catch (...) {
// 处理未知异常
std::cerr << "Unknown exception in destructor" << std::endl;
}
}
private:
void someMethodThatMayThrow() {
// 可能抛出异常的操作
}
};
2.2、避免在析构函数中调用可能抛出异常的方法
- 将可能抛出异常的操作一道析构函数中,可以类的公共方法中调用,在析构函数中只处理不会抛出异常的操作
class MyClass {
public:
void cleanup() {
// 可能抛出异常的代码
someMethodThatMayThrow();
}
~MyClass() noexcept {
// 只处理不会抛出异常的操作
}
private:
void someMethodThatMayThrow() {
// 可能抛出异常的操作
}
};
3、总结
- 析构函数绝对不要触发异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉该异常,然后捕获他们,不进行异常传播或结束程序
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作
思维导图笔记