代码之家  ›  专栏  ›  技术社区  ›  xmh0511

return语句发生异常的问题

  •  1
  • xmh0511  · 技术社区  · 4 年前
    #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问题中也被问过,这个问题不是我问题的主题。

    我真的不明白这个例子,如何解释这些提问?

    1 回复  |  直到 4 年前
        1
  •  2
  •   Nicol Bolas    4 年前

    好吧,即使我同意在#1和#2将创建这些类型为A的临时对象,

    没有提到“临时对象”(这是C++中的一个特殊术语,具有明确的含义,在这里不适用)。return语句初始化函数返回的prvalue。

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

    • 如果初始化器是(无括号)带括号的init列表或是=带括号的初始化列表,则对象或引用将被列表初始化。

    因此,此复制初始化实例将立即推迟到列表初始化。还有 list-initialization 这里不需要创建任何临时对象来从带括号的init列表的给定成员初始化prvalue。

    所以没有临时工;只有返回值对象,它通过列表初始化由带括号的init列表初始化。