为什么临时生命周期延长会导致析构函数被多次调用?

时间:2019-01-31 08:30:58

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

请考虑以下代码段:

#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()在这里被调用了两次(但仅构造了一个临时obj)。这似乎很奇怪,因为1)应该将GetTemp()返回的temp变量的寿命延长到完整表达式,以及2)因为我们直接在temp中返回了TakeTemp,所以返回值优化将重用同一对象。

有人可以解释为什么这里有多个dstor呼叫吗?

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

2 个答案:

答案 0 :(得分:5)

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

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

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

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

答案 1 :(得分:1)

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

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

函数调用GetTemp()是一个prvalue。由于它是函数调用的参数,因此其结果对象是匹配的参数Temp temp。在构建Temp temp时应用临时物化转换

请注意,GetTemp()函数内部没有创建临时文件。语句return Temp();并不意味着要创建一个对象。它提供了用于以后最终创建对象的参数。在实现prvalue之前,不会创建任何对象。

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

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

    输入
  • GetTemp正文
  • GetTemp return语句已执行,它初始化了参数Temp temp
  • GetTemp主体退出(如果有的话,销毁任何局部变量)
  • 输入
  • TakeTemp正文
  • TakeTemp return语句被执行,它初始化TakeTemp的返回值对象
  • TakeTemp正文退出(执行返回到main
  • {参数Temp temp被破坏
  • {TakeTemp的返回值对象已损坏

Temp temp的生存期是函数参数的生存期;函数返回后将其销毁。

返回值TakeTemp的生存期是一个临时对象的生存期,因此它将一直持续到完整表达式的结尾。

请注意,有一个古怪的函数参数为生存期:it is implementation-defined是在调用之后还是在完整表达式结束时销毁。因此,以上列表中的最后两个步骤可以按任意顺序发生。在我安装的g ++ 8.2.1中,function参数实际上是两个析构函数中的较晚一个。