You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从析构函数抛出异常时的编译器差异探究

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标准要求,程序的执行步骤如下:

  1. try块内依次构造A a('a')、Y y、A b('b')。
  2. 执行return {'c'}时,先构造返回值临时对象A('c')。
  3. 开始按构造逆序销毁try块内的局部对象:
    • 销毁A b('b'),输出b;
    • 销毁Y y时,其析构函数抛出异常,触发catch(...);
    • 进入异常处理流程后,需销毁try块内剩余的局部对象A a('a'),输出a;
    • 同时,已经构造完成的返回值临时对象A('c')必须被销毁,输出c;
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 11:22:53