std::uncaught_exception() 在 C++20 中已被移除,因其无法可靠判断异常传播状态;应改用 std::uncaught_exceptions() 结合差值法,并确保析构函数不抛异常。

std::uncaught_exception() 在 C++17 中已被弃用,在 C++20 中彻底移除。它**不能可靠用于判断“当前是否处于异常传播中”**,更不能作为析构函数安全决策的依据。
为什么 std::uncaught_exception() 不可信
该函数仅表示“有异常被抛出但尚未被处理”,不区分异常是否正在栈展开、是否已进入 catch 块、甚至是否已被 std::terminate 调用。在 catch 块内、或异常处理中途再次抛出新异常时,行为极易误判。
- 在
catch块中调用它返回true,但此时异常已“被捕获”,并非“未捕获” - 在析构函数中调用它,无法区分是普通销毁还是因栈展开触发的销毁
- 多线程下无任何同步保证,结果不可预测
C++11 及以后的替代方案:std::uncaught_exceptions()
std::uncaught_exceptions()(注意复数形式)返回当前未完成处理的异常数量(int),自 C++17 起标准化,C++11 起部分编译器已支持。它能更精确反映异常栈展开深度。
典型用法是“异常计数差值法”,用于判断析构是否发生在栈展开期间:
立即学习“C++免费学习笔记(深入)”;
class Guard {
int m_uncaught;
public:
Guard() : m_uncaught(std::uncaught_exceptions()) {}
~Guard() {
// 析构时若异常计数变高,说明正处在栈展开中
if (std::uncaught_exceptions() > m_uncaught) {
// 避免抛出异常、避免调用可能抛异常的函数
// 例如:只做日志记录(确保 logger 不抛异常)、释放裸指针等
} else {
// 正常析构路径,可安全执行完整清理逻辑
}
}
};
析构函数中真正安全的做法
无论是否检测异常状态,析构函数都应遵循“noexcept by default”原则。C++11 起析构函数默认为 noexcept(true),一旦抛出异常且此时已有未处理异常,程序直接调用 std::terminate。
- 所有资源管理类(如 RAII 类)的析构函数必须不抛异常 —— 使用
try/catch吞掉内部可能抛出的异常 - 避免在析构中调用用户可重载/可配置的函数(如虚函数、回调),除非你能 100% 确保它们
noexcept - 日志、调试输出等辅助操作,需使用不抛异常的底层 API(如
write(2)、std::fputs) - 对智能指针(
std::unique_ptr、std::shared_ptr)而言,其析构本身是安全的;但自定义删除器必须是noexcept
容易被忽略的关键点
很多人以为“只要没在 catch 里就安全”,其实不是。即使你写了一个看似独立的析构函数,只要它被栈展开触发,且内部调用了可能抛异常的代码(比如 std::string 的隐式分配、std::vector::push_back),就会导致 std::terminate —— 而这个过程不会给出任何提示。
真正可靠的防线不是检测异常,而是让析构函数本身成为“纯机械动作”:只操作原始指针、调用 free/close/pthread_mutex_destroy 等明确不抛异常的系统级接口,并把所有带异常风险的操作移到 reset()、release() 或显式 cleanup() 成员函数中。










