考虑以下片段:
#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)GetTemp()
返回的临时变量的生存期应该延长到完整的表达式,2)由于我们在TakeTemp
中直接返回temp
,返回值optimization将重用同一对象。
有人能解释为什么这里有多个dstor电话吗?
(注意,如果我们放置更多的TakeTemp()层,dstor调用的数量会成比例增长。)
TakeTemp
按值获取参数,并按值返回参数。
您正在那里进行复制,因此现在有两个Temp
对象要删除。
您看到的两个被破坏的对象是这里调用的两个函数的返回值:
TakeTemp(GetTemp());
^ returns a Temp
^ returns a Temp
使用C++17术语,这两个对象是:
- 函数参数
Temp temp;
TakeTemp
的返回值
函数调用GetTemp()
是一个prvalue。由于它是函数调用的参数,因此其结果对象是匹配的参数Temp temp
。临时物化转换应用于Temp temp
的构建点。
请注意,GetTemp()
函数内部没有临时创建。语句return Temp();
并不意味着要创建一个对象;它给出了稍后将用于最终创建对象的参数。在实现prvalue之前,不会创建任何对象。
然后,执行return temp;
创建第二个对象。这与return Temp();
不同,因为temp
是一个左值,而不是一个prvalue。函数调用TakeTemp
的返回值对象是使用temp
作为初始值设定项创建的。这不是一个复制省略上下文。如果您向Temp
添加一个复制构造函数,您将看到该对象的复制构造消息。
概括一下,事件的顺序是:
GetTemp
主体已输入- 执行
GetTemp
返回语句,初始化参数Temp temp
GetTemp
主体退出(销毁任何局部变量(如果有的话))- 输入
TakeTemp
正文 - 执行
TakeTemp
返回语句,初始化TakeTemp
的返回值对象 TakeTemp
主体退出(执行返回到main
)- {参数
Temp temp
已销毁 - {
TakeTemp
的返回值对象已销毁
Temp temp
的寿命是函数参数的寿命;它在函数返回后被销毁。
TakeTemp
的返回值的生存期是一个临时对象生存期,因此它一直持续到完整表达式结束。
请注意,函数参数生存期有一个怪癖:它是由实现定义的,无论是在调用后立即销毁,还是在完整表达式结束时销毁。因此,我上面列表中的最后两个步骤可以按任意顺序进行。在我安装g++8.2.1时,函数参数实际上是两个析构函数中较晚的一个。