【问题标题】:Are function return values automatic objects and thus guaranteed to be destructed?函数返回值是否是自动对象并因此保证被破坏?
【发布时间】:2016-04-12 22:10:54
【问题描述】:

在 [except.ctor] 中,标准 (N4140) 保证:

...调用析构函数 自从 try 块被构造的所有自动对象 输入...

但是在下面的例子中,空的output证明了函数foo的返回值没有被破坏,尽管它已经被构造了。使用 g++ (5.2.1) 和 clang++ (3.6.2-1) 和选项 -O0 -fno-elide-constructors -std=c++14 编译。

struct A { ~A() { cout << "~A\n"; } };

struct B { ~B() noexcept(false) { throw 0; } };

A foo() {
  B b;
  return {};
}

int main() {
  try { foo(); }
  catch (...) { }
}

这是 g++ 和 clang++ 中的错误,还是函数返回值不是 被认为是自动对象,还是 C++ 语言中的一个漏洞?

在 [stmt.return]、[expr.call] 或 [dcl.fct] 中我都找不到 明确说明函数返回值是否被视为自动 目的。我发现的最接近的提示是 6.3.3 p2:

...return 语句可以 涉及临时对象的构造和复制或移动...

和 5.2.2 p10:

如果结果类型是左值,则函数调用是左值 引用类型或对函数类型的右值引用,如果 结果类型是对对象类型的右值引用,否则是纯右值。

【问题讨论】:

    标签: c++ exception-handling return return-value language-lawyer


    【解决方案1】:

    我修改了你的代码,我认为现在从输出中我们可以看到 A 没有被破坏。

    #include<iostream>
    
    using namespace std;
    
    struct A {
        ~A() { cout << "~A\n"; }
        A() { cout << "A()"; }
    };
    
    struct B {
        ~B() noexcept( false ) { cout << "~B\n"; throw(0); }
        B() { cout << "B()"; }
    };
    
    A foo() {
        B b;
        return;
    }
    
    int main() {
        try { foo(); }
        catch (...) {}
    }
    

    输出是:

    B()A()~B

    所以是的,这可能是一个错误。

    【讨论】:

      【解决方案2】:

      函数返回值被认为是临时的,返回值的构造在locals的销毁之前排序。

      不幸的是,标准中没有详细说明这一点。有一个 open defect 对此进行了描述并提供了一些措辞来解决此问题

      [...] 带有 void 类型操作数的 return 语句只能在返回类型为 cv void 的函数中使用。带有任何其他操作数的 return 语句只能在返回类型不是 cv void 的函数中使用; return 语句初始化要通过复制初始化(8.5 [dcl.init])从操作数返回的对象或引用。 [...]

      返回实体的复制初始化在由 return 语句的操作数建立的完整表达式末尾处的临时对象销毁之前排序,而后者又在局部变量的销毁之前排序(6.6 [stmt.jump]) 包含 return 语句的块。

      由于函数返回值是临时的,它们不在帖子开头的 destructors are invoked for all automatic objects 引用中涵盖。但是,[class.temporary]/3 说:

      [...] 临时对象被销毁作为评估完整表达式的最后一步,该完整表达式(词法上)包含它们被创建的点。 即使评估以抛出异常结束也是如此。 [...]

      所以我认为你可以认为这是 GCC 和 Clang 中的错误。

      不要从析构函数中抛出 ;)

      【讨论】:

      • 我发现 gcc 和 clang 多年来都针对他们提出了这个错误,所以我不希望他们会很快修复它:gccclang
      • 在这种情况下允许编译器省略对象A的构造吗?我的意思是一些类似于 RVO 的规则。
      • @Mikhail 我不这么认为。 RVO不能完全消除构造,它可以消除中间构造。
      【解决方案3】:

      这是一个错误,这一次,MSVC 确实做到了:它打印“~A”。

      【讨论】:

      • 有时会看到 clang 和 gcc 拒绝代码,但 MSVC 接受具有定义行为的代码。我也遇到过一次,如果有人问我可以举个例子。
      • 能否请您添加对标准的引用,以证明函数返回值被视为自动对象,或者以其他方式证明它确实是 gcc 和 clang 中的错误?
      • @FlorianKaufmann 我认为this example 表明有一个A 对象永远不会为其调用析构函数,即使它在创建后超出了范围。
      • @FlorianKaufmann TartanLlma 对您帖子下方的缺陷报告的引用似乎完全解决了手头的问题(是在局部变量 b 之前构造的 A 类型的待返回临时对象是破坏?)。答案应该是肯定的,但没有明确说明。答案可能是肯定的,因为本地人参与将返回值放在一起的明显用例。
      • @MSalters。 “还能是什么?”没错,我也有同样的感受。然而,这不是证据。我更喜欢确认并查看标准中的相关段落。我们从经验中知道,C++ 语言有很多惊喜。
      猜你喜欢
      • 2012-06-20
      • 2018-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-26
      • 2021-10-04
      • 2021-10-26
      相关资源
      最近更新 更多