return 语句出现异常的问题

The issue about the exception which occurs with return statement

#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的变体例子,我对这个例子及其在标准中的相应注释有很多疑问,即:

At #1, the returned object of type A is constructed. Then, the local variable b is destroyed ([stmt.jump]). Next, the local variable y is destroyed, causing stack unwinding, resulting in the destruction of the returned object, followed by the destruction of the local variable a. Finally, the returned object is constructed again at #2.

首先,在#1,为什么要创建类型 A 的对象?关于 return statement 的规则说:

the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.

return statement的操作数是:

The expr-or-braced-init-list of a return statement is called its operand.

这意味着 #1 处的大括号初始化列表 {3} 是操作数,调用的结果将被该操作数复制初始化,最后两句打印出来的句子证明了这一点意见。

好吧,即使我同意在 #1#2 将创建这些 A 类型的临时对象,但是我不同意临时对象的销毁顺序对象和局部变量,我的观点是 return statement 规则所说的:

stmt.jump#stmt.return-3

The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables ([stmt.jump]) of the block enclosing the return statement.

IIUC,由 return statement 的操作数创建的这些临时变量的销毁应该在这些局部变量的销毁之前排序。那么,为什么注释说“接下来,局部变量 y 被销毁,导致堆栈展开,导致返回对象的销毁”?根据上面的规则,临时变量的销毁应该发生在局部变量 y 之前,例外规则是:

The destructor is invoked for each automatic object of class type constructed, but not yet destroyed, since the try block was entered.

此时,也就是y的销毁,return statement的操作数创建的临时对象已经销毁了,不是吗?

并且id 3对象的破坏永远不会被评估,但是这个问题已经在其他SO问题中被问到,这个问题不是我的问题的主题。

这个例子我实在是看不懂,请问怎么解释?

Well, even if I agree that at #1 and #2 will create these temporary objects of type A,

没有提到“临时对象”(这是 C++ 中的一个特殊术语,具有 well-defined 的含义,此处不适用)。 return 语句初始化由函数编辑的纯右值 return。

如果搜索初始化,第一个要点[dcl.init]/17 has is

  • If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.

所以这个复制初始化的实例将立即推迟到列表初始化。 list-initialization 这里不需要创建任何临时变量来初始化 braced-init-list.

的给定成员的纯右值

所以没有临时工;只有 return 值对象,它由 braced-init-list 到 list-initialization.

初始化