#include <iostream>
struct A {
A(int id):id_(id){
std::cout<<"construct A with id: "<<id_<<"\n";
}
int id_;
~A(){
std::cout<<"destory A with id: "<< id_<<"\n";
}
A(A const&) = default;
};
struct Y {
~Y() noexcept(false) {
std::cout<<"destory Y\n";
throw 0;
}
};
A f() {
try {
A a(1);
Y y;
A b(2);
return {3}; // #1
} catch (...) {
std::cout<<"handle exception\n";
}
A dd(4);
return {5}; // #2
}
int main(){
auto t = f();
std::cout<<"in main\n";
}
它的
outcomes
是(GCC和Clang给出相同的结果):
construct A with id: 1
construct A with id: 2
construct A with id: 3
destory A with id: 2
destory Y
destory A with id: 1
handle exception
construct A with id: 4
construct A with id: 5
destory A with id: 4
in main
destory A with id: 5
考虑这个例子,这是一个变体示例
except.ctor#2
,我对这个例子及其在标准中的相应注释有很多疑问,那就是:
在#1,构造类型为A的返回对象。然后,局部变量b被销毁([stmt.jump])。接下来,局部变量y被销毁,导致堆栈展开,导致返回的对象被销毁,接着是局部变量a的销毁。最后,在#2处再次构造返回的对象。
首先,在#1,为什么要创建A类型的对象?关于的规则
return statement
说:
return语句通过从以下位置复制初始化来初始化(显式或隐式)函数调用的glvalue结果或prvalue结果对象
操作数
.
以及操作数
return语句
是:
这个
expr或带括号的初始化列表
return语句的函数被调用
其操作数
.
这意味着括号初始化列表
{3}
在
#1
是操作数,调用的结果将由该操作数进行复制初始化,最后两个打印出来的句子证明了这一观点。
好吧,即使我同意
1.
和
#2
将创建以下类型的临时对象
A
但是,我不同意临时对象和局部变量的销毁顺序,我的观点是
return语句
规则说:
stmt.jump#stmt.return-3
调用结果的复制初始化在return语句的操作数建立的完整表达式末尾的临时值被销毁之前进行排序,
反过来,它在包含return语句的块的局部变量([stmt.jump])被销毁之前被排序
.
IIUC,销毁由操作数创建的这些临时对象
return语句
应该在这些局部变量被破坏之前进行排序。那么,为什么注释说“接下来,局部变量y被破坏,导致堆栈展开,从而导致返回对象的破坏”呢?根据上述规则,临时变量的销毁应先于局部变量的销毁
y
,例外规则说:
为构造的类类型的每个自动对象调用析构函数,
但尚未销毁
,因为已进入try块。
此时,即销毁
y
,由操作数创建的临时对象
return语句
已经毁了,不是吗?
以及物体的破坏
id 3
从未被评估过,但这个问题在其他SO问题中也被问过,这个问题不是我问题的主题。
我真的不明白这个例子,如何解释这些提问?