从析构函数抛出异常时的编译器差异探究
C++17标准下cppQuiz #323的输出差异分析
题目代码
#include <iostream> #include <stdexcept> struct A { A(char c) : c_(c) {} ~A() { std::cout << c_; } char c_; }; struct Y { ~Y() noexcept(false) { throw std::runtime_error(""); } }; A f() { try { A a('a'); Y y; A b('b'); return {'c'}; } catch (...) { } return {'d'}; } int main() { f(); }
预期输出与实际差异
根据C++17标准的[except.ctor]条款,该程序的预期输出应为bcad,但实际编译器测试结果不同:
- Clang 16.0 输出
bad - GCC 13.1 输出
bacd
标准规定的执行流程
按照C++17标准要求,程序的执行步骤如下:
try块内依次构造A a('a')、Y y、A b('b')。- 执行
return {'c'}时,先构造返回值临时对象A('c')。 - 开始按构造逆序销毁
try块内的局部对象:- 销毁
A b('b'),输出b; - 销毁
Y y时,其析构函数抛出异常,触发catch(...); - 进入异常处理流程后,需销毁
try块内剩余的局部对象A a('a'),输出a; - 同时,已经构造完成的返回值临时对象
A('c')必须被销毁,输出c;
- 销毁
catch块执行完毕后,执行return {'d'},构造A('d'),函数返回后该对象被销毁,输出d。
最终拼接输出为bcad。
编译器差异的具体原因
GCC 13.1的bacd输出
GCC在异常处理流程中,销毁对象的顺序与标准规定不一致。当Y y析构抛出异常后,GCC优先销毁try块内剩余的局部对象A a('a')(输出a),再销毁返回值临时对象A('c')(输出c),而非标准要求的先销毁返回值临时对象。这导致输出顺序变为b→a→c→d,即bacd。
Clang 16.0的bad输出
Clang启用了返回值优化(RVO),直接将return {'c'}的返回值对象构造在main函数的栈帧中。当Y y的析构函数抛出异常时,Clang认为该返回值对象未被函数正常返回使用,因此不触发其析构,c没有被输出。后续流程中,销毁A b('b')(输出b)、A a('a')(输出a),进入catch块后返回A('d'),函数结束后销毁该对象(输出d),最终输出bad。
内容的提问来源于stack exchange,提问作者user3550394
相关产品推荐
相关产品推荐

