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

huangapple go评论65阅读模式
英文:

Compiler differences when throwing exception from a destructor

问题

根据C++17标准,这个程序的输出是什么?

#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();
}

这个问题的预期答案是 "bcad",参考 [except.ctor] 最近的标准。
然而,根据godbolt,clang 16.0 返回 "bad",gcc 13.1 返回 "bacd"。
有人知道发生了什么吗?

英文:

I have found following C++ question (cppQuiz #323):

According to the C++17 standard, what is the output of this program?

#include &lt;iostream&gt;
#include &lt;stdexcept&gt;

struct A {
    A(char c) : c_(c) {}
    ~A() { std::cout &lt;&lt; c_; }
    char c_;
};

struct Y { ~Y() noexcept(false) { throw std::runtime_error(&quot;&quot;); } };

A f() {
    try {
        A a(&#39;a&#39;);
        Y y;
        A b(&#39;b&#39;);
        return {&#39;c&#39;};
    } catch (...) {
    }
    return {&#39;d&#39;};
}

int main()
{
    f();
}

The expected answer for the question is "bcad" with a reference to [except.ctor] in the recent standard.
However, according to godbolt, clang 16.0 returns "bad" and gcc 13.1 returns "bacd".

Do anyone know what happens?

答案1

得分: 4

翻译如下:

输出应该是 bcad

当第一个返回语句被执行时,f 调用的结果对象,即从被弃用值表达式 f()main 中生成的临时对象,将在任何本地变量或临时对象被销毁之前初始化。请参考 [stmt.return]/3

在函数本身中没有需要销毁的临时对象。

然后,本地变量按初始化顺序的相反顺序被销毁。

首先销毁 b,输出 b

然后销毁 y 并引发异常。异常被 catch 处理程序捕获,因此将发生堆栈展开。

堆栈展开将销毁作用域中的自动存储期对象,以及已构造的结果对象,按它们的构造顺序的相反顺序,只要它们尚未被销毁。请参考 [except.ctor]/2,它实际上具有一个非常类似的示例,并由 CWG 2176 澄清。

by 已经被销毁。因此,只有结果对象和 a 保留下来。结果对象是在 a 之后构造的。

堆栈展开将从销毁已经构造的结果对象开始,输出 c

然后销毁 a,输出 a,并完成堆栈展开。

执行 catch 处理程序后,最终达到 return 语句,再次构造结果对象,然后在 mainf() 的完整表达式的末尾销毁它以输出 d


编译器似乎尚未实现 CWG 2176 的缺陷报告,而在那之前,行为应该是不清楚的。

至于为什么他们还没有实现它,我猜这是一个不太常见的情景,他们可能认为它的优先级较低。具有抛出异常的析构函数是不寻常的,正如问题所显示的,用户对销毁顺序的期望可能与标准不同。但是,我认为 Clang 完全跳过已构造的结果对象的销毁行为可能有些问题。

英文:

The output ought to be bcad.

When the first return statement is reached, the result object of the call to f, which is the temporary object materialized in main from the discarded-value expression f(), will be initialized before any local variables or temporaries are destroyed. See [stmt.return]/3.

There are no temporary objects in the function itself that would be destroyed.

So then local variables are destroyed in reverse order of initialization.

First b is destroyed, outputting b.

Then y is destroyed and throws the exception. The exception is caught by the catch handler and therefore stack unwinding will happen.

Stack unwinding will destroy objects of automatic storage duration in the scope, as well as the already constructed result object, in reverse order of their construction, as long as they haven't been destroyed yet. See [except.ctor]/2 which actually has a very similar example and was clarified by CWG 2176.

b and y have already been destroyed. So only the result object and a remain. The result object was constructed after a.

Stack unwinding will therefore start by destroying the already constructed result object, outputting c.

Then a is destroyed, outputting a, and stack unwinding concludes.

After executing the catch handler, the final return statement is reached, which once again constructs the result object, which in turn is destroyed at the end of the full-expression f() in main to output d.


The compiler seem to not have implemented the defect report for CWG 2176 yet and before that it wasn't clear what the behavior should be.

As for why they haven't implemented it yet, I would guess that this is such an unusual scenario, that they would consider it low priority. Having a throwing destructor is unusual and as the question shows, it is unlikely that the user would have the same expectation on the order of destruction as the standard does. However, I think that Clang's behavior of completely skipping the destruction of the already constructed result object is bit problematic.

huangapple
  • 本文由 发表于 2023年5月29日 21:25:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76357782.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定