英文:
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 <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();
}
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 澄清。
b
和 y
已经被销毁。因此,只有结果对象和 a
保留下来。结果对象是在 a
之后构造的。
堆栈展开将从销毁已经构造的结果对象开始,输出 c
。
然后销毁 a
,输出 a
,并完成堆栈展开。
执行 catch
处理程序后,最终达到 return
语句,再次构造结果对象,然后在 main
中 f()
的完整表达式的末尾销毁它以输出 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论