【问题标题】:The issue about the exception which occurs with return statement关于return语句出现异常的问题
【发布时间】:2020-09-18 03:44:57
【问题描述】:
#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 语句通过从操作数复制初始化来初始化(显式或隐式)函数调用的泛左值结果或纯右值结果对象。

return statement的操作数是:

return 语句的 expr-or-braced-init-list 称为其操作数

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

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

stmt.jump#stmt.return-3

调用结果的复制初始化在return语句的操作数建立的完整表达式末尾处的临时对象销毁之前排序,反过来,在销毁之前排序包含 return 语句的块的局部变量([stmt.jump])

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

自进入 try 块以来,为每个已构造、但尚未销毁的类类型自动对象调用析构函数。

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

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

这个例子我真的看不懂,这些问怎么解释?

【问题讨论】:

    标签: c++ exception c++17 language-lawyer


    【解决方案1】:

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

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

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

    • 如果初始值设定项是(无括号的)大括号初始化列表或 = 大括号初始化列表,则对象或引用是列表初始化的。

    所以这个副本初始化实例将立即推迟到列表初始化。并且这里的list-initialization 不需要创建任何临时变量来初始化来自braced-init-list 的给定成员的prvalue。

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

    【讨论】:

    • 你的意思是这句话“在#1,构造了返回的类型A的对象”。其中returned object 指的是prvalue result object
    • @jackX:嗯,是的。它还指的是什么?
    • 好吧,我曾经认为returned object 会是temporary object,这是我的错误解读。但是,请看这个code,对于returned object的销毁(即表达式f()的纯右值结果对象,这里是main中的t)仍然没有在对象@987654332之前被销毁@,它的行为与注释中所说的不同。如何解释这个?
    • @jackX:你知道标准的文本说一件事是什么意思,但编译器生成的代码做另一件事。
    • @jackX 正如它正确指出的那样,在 #1 中初始化的结果对象不是临时对象。
    猜你喜欢
    • 2010-12-09
    • 2018-10-28
    • 2020-07-18
    • 2010-09-24
    • 2013-06-14
    • 1970-01-01
    • 2018-03-15
    • 1970-01-01
    • 2011-04-15
    相关资源
    最近更新 更多