函数参数不会在该函数的出口处销毁

时间:2014-11-28 20:59:43

标签: c++

我很确定,应该在相应函数的出口调用函数参数的析构函数。考虑5.2.2p4的C ++ 11标准:

  

[...]参数的生命周期在定义它的函数返回时结束。 [...]

但是,让我们试试这段代码:

#include <iostream>
using namespace std;

struct Logger {
    Logger(int) { cout << "Construct " << this << '\n'; }
    Logger(const Logger&) { cout << "Copy construct " << this << '\n'; }
    ~Logger() { cout << "Destruct " << this << '\n'; }
};

int f(Logger)
{
    cout << "Inside f\n";
    return 0;
}

int main()
{
    f(f(f(10)));
}

使用gcc或clang编译后,输出将如下:

Construct 0x7fffa42d97ff
Inside f
Construct 0x7fffa42d97fe
Inside f
Construct 0x7fffa42d97fd
Inside f
Destruct 0x7fffa42d97fd
Destruct 0x7fffa42d97fe
Destruct 0x7fffa42d97ff

正如我们所看到的,所有三个参数仅在最后一个函数调用完成后被销毁。这是正确的行为吗?

4 个答案:

答案 0 :(得分:7)

参见C ++ 11标准,§12.2/ 3,说

  

临时对象作为评估的最后一步被销毁   完整表达(1.9)(词法上)包含它们的点   被创造了。

答案 1 :(得分:4)

详细说明现在已经可以在评论中找到:

给定int f(Logger);,当你写:

f(10);

this(概念上)构造一个临时的Logger对象,从该临时对象构造函数参数,调用函数,销毁函数参数,最后销毁临时对象。

当你写:

f(f(10));

这个(概念上)构造一个临时的Logger对象,从该临时对象构造函数参数,调用函数,销毁函数参数,使用第一个函数调用构造一个新的临时Logger对象& #39; s结果,从构造函数参数临时对象,调用函数,销毁函数参数,最后销毁这两个临时对象。

我不会为f(f(f(10)));案例写出来。

现在,可以省略这两个临时对象:

  

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间。没有优化就被破坏了。这种复制/移动的省略   在以下情况下允许称为 copy elision 的操作(可以合并以消除多个副本):

     
      
  • ...

  •   
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-unqualified类型的类对象时,可以通过构造它来省略复制/移动操作临时对象直接进入省略的复制/移动目标

  •   
  • ...

  •   

由于函数参数和临时对象具有相同的类型,因此允许编译器将它们视为同一对象。临时对象将在最后阶段被销毁,因此参数的生命周期不起作用。

但是,当执行复制省略 时,例如因为您没有配置编译器,或者因为首先没有复制到elide(见下文),那么当你说它们时,必须确实破坏了函数参数,你必须看到&#34; Destruct(...)&#34;在所有符合C ++ 11实现的第二个函数调用开始之前。

使用大括号可以在没有临时的情况下构建参数:您可以将调用重新设置为

f({f({f({10})})});

这里,每个参数都是列表初始化的,在这种情况下,它不涉及临时对象,并且没有要复制的副本。在所有符合C ++ 11的实现中,只要函数f返回,再次调用f之前,必须销毁函数参数,无论-felide-constructors是什么{{ 1}}命令行选项,以及编译器不执行此操作的事实是他们无法符合C ++ 11的区域。

它不是完全这么简单,但是:CWG issue 1880读取:

  

WG决定不指定参数对象是在调用之后立即销毁还是在调用所属的完整表达式结束时销毁。

这将允许编译器现在完全执行的操作:在最后一个f返回之后,可以在完整表达式结束后销毁参数。 C ++ 11的确切文字文本并不是当前编译器所实现的。

答案 2 :(得分:2)

这是正确的行为。它遵循破坏资源的先进先出方法。如果您按顺序调用了该函数,则会得到不同的结果。

f(10)
f(10)
f(10)

会像这样破坏:

Construct 0x7fffa42d97ff
Inside f
Destruct 0x7fffa42d97ff
Construct 0x7fffa42d97fe
Inside f
Destruct 0x7fffa42d97fe
Construct 0x7fffa42d97fd
Inside f
Destruct 0x7fffa42d97fd

答案 3 :(得分:2)

您期望看到的是:

Construct 0x7fffa42d97ff         //  Creation of temporary object
Copy construct 0xAAAAAAAAAA      //  copy constuction of parameter
Inside f
Destruct 0xAAAAAAAAAA            // destruction of parameter.
Construct 0x7fffa42d97fe
Copy construct 0xBBBBBBBBB
Inside f
Destruct 0xBBBBBBBBB
Construct 0x7fffa42d97fd
Copy construct 0xCCCCCCCCC
Inside f
Destruct 0xCCCCCCCCC
Destruct 0x7fffa42d97fd
Destruct 0x7fffa42d97fe
Destruct 0x7fffa42d97ff          // destruction of temporary

但是允许编译器删除(删除)参数(及其析构函数)的复制结构并内联函数。如果这样做,构造的唯一剩余对象是传递给函数的临时对象。

因此,如果您取结果集删除复制结构(由积极的编译器优化引起),您将得到答案中显示的输出。

如果你想看到上面的输出。然后阻止编译器内联函数:请参阅https://stackoverflow.com/a/1474050/14065

注意:内衬只是消除副本的一个原因。编译器可以使用其他几个。我在内嵌中使用了这个例子,因为它最容易可视化删除被复制到函数中的参数。