析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?
在我们不做过多处理的情况下,析构函数是绝对不能抛出异常的,这会直接导致我们的整个程序被强制终止
现象
#include <iostream> #include <stdexcept> using namespace std; class Bad { public: ~Bad() { // C++11起默认 noexcept cout << "~Bad: 准备抛异常\n"; throw runtime_error("析构里抛的异常"); } }; int main() { try { Bad a; // a 离开作用域,析构 } catch (const exception& e) { cout << "捕获到: " << e.what() << "\n"; } cout << "main 正常结束\n"; // 这行不会执行 }C++关于异常处理的两点规则:
在栈展开过程中,如果析构函数抛出异常且当前已有未捕获异常,会直接调用
std::terminate。析构函数默认隐式
noexcept,只要它内部抛出异常,无论当前有没有其他异常,都会直接调用std::terminate。
class Bad2 { public: ~Bad2() noexcept(false) { // 显式声明可能抛 throw runtime_error("析构异常"); } };我们可以通过显式指定noexcept(false)的方式去告诉编译器,我们这个析构函数可以抛出异常,这样就会被成功地捕获到
class Leaky { FILE* f; public: Leaky() { fopen_s(&f, "a.cpp", "w"); } ~Leaky() noexcept(false) { cout << "~Leaky: 准备抛异常\n"; // ① 清理 throw runtime_error("boom"); // ② 抛出,析构后面的逻辑全跳过 cout << "这行永远不执行\n"; // 演示用 fclose(f); } };不过我们在实际的编程中也不建议去显式的指定析构函数可以抛异常,也不建议去让它抛异常,因为这很可能导致一些资源的泄露。比如上面这个代码在抛出异常之后,异常后面的代码是永远不会被执行的,如果后面是会去释放一些大量资源,这就会导致内存泄漏
真正致命的——栈展开时再抛
class Bad2 { public: ~Bad2() noexcept(false) { cout << "~Bad2 析构,再抛一个\n"; throw runtime_error("析构异常2"); } }; void foo() { Bad2 a; throw runtime_error("原始异常"); // 触发栈展开 → a析构 → 再抛 } int main() { try { foo(); } catch (const exception& e) { cout << "抓到: " << e.what() << "\n"; } }正确做法
class Good { public: ~Good() noexcept { try { cleanup(); // 可能失败的操作 } catch (...) { // 吞掉,记日志,别让它跑出去 cerr << "析构清理失败,已忽略\n"; } } void cleanup(); // 用户可以显式调用并处理错误 // 进阶:提供 close(),让用户主动调用、主动 catch void close() { /* 真正可能失败的清理 */ } };析构里try-catch(...)吞掉所有异常;如果清理真的可能失败且用户该知道,就提供close()让用户显式调用。
