【问题标题】:Why would temporary lifetime extension cause destructor to be called multiple times?为什么临时延长寿命会导致多次调用析构函数?
【发布时间】:2019-01-31 08:30:57
【问题描述】:

考虑以下 sn-p:

#include <iostream>

using namespace std;

class Temp {
    public:
    Temp() { cout << "Temp()" << endl;}
    ~Temp() { cout << "~Temp()" << endl;}
};

Temp GetTemp() {
     cout << "GetTemp" << endl;
    return Temp();
}

Temp TakeTemp(Temp temp) {
    cout << "TakeTemp" << endl;
    return temp;
}


int main()
{
    TakeTemp(GetTemp());

    return 0;
}

当我运行TakeTemp(GetTemp()); 时,输出看起来像

GetTemp                                                                                                                                                        
Temp()                                                                                                                                                         
TakeTemp                                                                                                                                                       
~Temp()                                                                                                                                                        
~Temp()     

请注意,~Temp() 在这里被调用了两次(但只构造了 1 个临时 obj)。这看起来很奇怪,因为 1) GetTemp() 返回的临时变量的生命周期应该扩展到完整的表达式,以及 2) 因为我们直接在 TakeTemp 中返回 temp,所以返回值优化将重用相同的对象。

谁能解释为什么这里有多个 dstor 调用?

(请注意,如果我们在周围放置更多的 TakeTemp() 层,则 dstor 调用的数量会成比例地增长。)

【问题讨论】:

  • 在那里添加了完整的程序。
  • 这里也没有延长生命周期(到目前为止发布的代码部分)。临时的生命周期是直到完整表达式的结尾。
  • 不是完整的表达式TakeTemp(GetTemp());吗?在这种情况下,相同的临时变量将一直存在。
  • 是的,这就是完整的表达方式。问题是你用“延长”这个词来描述它的自然寿命是什么
  • 正在调用隐式复制构造函数。添加Temp(Temp const&amp;) { cout &lt;&lt; "Temp(Temp const&amp;)" &lt;&lt; endl;}

标签: c++ c++11 lifetime temporary xvalue


【解决方案1】:

您的函数TakeTemp 按值获取参数,并按值返回参数。

您正在那里制作副本,因此现在有两个 Temp 对象要删除。

您看到的被破坏的两个对象是此处调用的两个函数的返回值:

TakeTemp(GetTemp());
         ^ returns a Temp
^ returns a Temp

【讨论】:

  • 为什么 cstor 只调用一次呢?
  • @OneZero 因为您没有记录复制/移动构造函数。
  • @OneZero:因为你只跟踪默认构造函数,而拷贝使用拷贝构造函数。
  • 去添加一个Temp(const Temp&amp; other) { cout &lt;&lt; "Temp(other)" &lt;&lt; endl;}形式的复制构造函数,也可以在你的程序输出中看到它。没有它,编译器会为你生成一个默认的复制构造函数。
  • @rubenvb 至少会出现一次,因为函数参数temp的复制无法省略。
【解决方案2】:

使用 C++17 术语,这两个对象是:

  1. 函数参数Temp temp;
  2. TakeTemp的返回值。

函数调用GetTemp() 是prvalue。因为它是函数调用的参数,所以它的结果对象是匹配的参数Temp temp临时实现转换Temp temp的构造点应用。

请注意,GetTemp() 函数内没有临时创建。声明return Temp(); 并不意味着创建一个对象;它给出的参数将用于稍后最终创建一个对象。在实现纯右值之前不会创建任何对象。

然后,return temp; 的执行创建了第二个对象。这与return Temp(); 不同,因为temp 是左值,而不是纯右值。对TakeTemp 的函数调用的返回值对象是使用temp 作为初始化器创建的。这不是复制省略上下文。如果您将复制构造函数添加到Temp,您将看到该对象的复制构造消息。

回顾一下,事件的顺序是:

  • GetTemp正文已输入
  • 执行GetTempreturn语句,初始化参数Temp temp
  • GetTemp body 退出(如果有的话,销毁任何局部变量)
  • TakeTemp正文已输入
  • 执行TakeTempreturn语句,为TakeTemp初始化返回值对象
  • TakeTemp body 退出(执行返回main
  • {参数Temp temp被销毁
  • {TakeTemp的返回值对象被销毁

Temp temp 的生命周期是函数参数的生命周期;函数返回后销毁。

TakeTemp的返回值的生命周期是一个临时的对象生命周期,所以会一直持续到完整表达式结束。

请注意,函数参数生命周期有一个怪癖:it is implementation-defined 无论是在调用后立即销毁还是在完整表达式结束时销毁。因此,我上面列表中的最后两个步骤可以按任意顺序发生。在我安装的g++ 8.2.1上,函数参数实际上是两个析构函数中的后者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-07
    • 2011-11-26
    • 1970-01-01
    • 2019-08-29
    • 2014-07-04
    • 1970-01-01
    相关资源
    最近更新 更多