为何std::scope_success析构函数要检查未捕获异常数≤构造时的值?
为什么std::scope_success析构函数用“小于或等于”而非“等于”?
答案很明确:确实存在析构时std::uncaught_exceptions()数值低于构造时的场景,最典型的就是「在栈展开过程中创建scope_success,之后异常被捕获处理」的情况。
举个实际代码例子就能明白:
#include <experimental/scope> #include <iostream> #include <stdexcept> void inner() { throw std::runtime_error("test error"); } void middle() { struct LocalObj { ~LocalObj() { // 此时处于栈展开阶段,异常尚未被捕获,std::uncaught_exceptions()返回1 std::experimental::scope_success guard{[](){ std::cout << "scope_success回调执行\n"; }}; // guard构造时,未捕获异常数是1 } }; try { LocalObj obj; inner(); // 抛出异常,触发栈展开,先析构obj } catch (const std::exception&) { // 异常被捕获,std::uncaught_exceptions()回到0 std::cout << "异常在middle中被处理\n"; } } int main() { try { middle(); } catch (...) {} }
这个例子的流程是:
- 进入
middle()的try块,构造LocalObj实例。 - 调用
inner()抛出异常,未捕获异常数变为1,开始栈展开,调用LocalObj的析构函数。 - 在析构函数里构造
scope_success,此时未捕获异常数是1。 LocalObj析构完成,回到catch块,异常被捕获,未捕获异常数降为0。- catch块执行完毕,
scope_success的生命周期结束,开始析构——此时未捕获异常数是0,明显小于构造时的1。
如果scope_success的析构函数只检查「等于」,那这个场景下回调就不会执行,但我们显然希望它执行:毕竟scope_success的设计初衷就是保证在对象生命周期结束时,只要不是因为新的未捕获异常导致的栈展开,就触发回调。
用「小于或等于」的逻辑,就能覆盖所有合理的执行场景:
- 正常退出时,析构未捕获数等于构造时的数值(都是0),触发回调;
- 栈展开全程未捕获异常时,析构未捕获数等于构造时的数值(都是N>0),触发回调;
- 栈展开中途异常被捕获,析构未捕获数小于构造时的数值,依然触发回调。
而「大于」的情况不可能出现:未捕获异常数只会在抛出时增加,捕获时减少;析构函数执行时如果抛出新异常,程序会直接终止,不会走到检查逻辑。
内容的提问来源于stack exchange,提问作者tmlen
相关产品推荐
相关产品推荐

