函数返回的类上的析构函数

Destructors on classes returned by functions

本文关键字:析构函数 函数 返回      更新时间:2023-10-16

我有以下代码:

#include <stdio.h>
class Foo {
public:
int a;
~Foo() { printf("Goodbye %dn", a); }
};
Foo newObj() {
Foo obj;
return obj;
}
int main() {
Foo bar = newObj();
bar.a = 5;
bar = newObj();
}

当我使用g++编译并运行它时,我得到:

Goodbye 32765
Goodbye 32765

打印的数字似乎是随机的。

我有两个问题:

  1. 为什么析构函数被调用两次?
  2. 为什么第一次没有打印5

我来自 C 背景,因此printf,我很难理解析构函数、何时调用它们以及如何从函数返回类。

让我们看看你的主函数会发生什么:

int main() {
Foo bar = newObj();

在这里,我们只是实例化一个Foo并使用返回值newObj()初始化它。这里没有因为复制省略而调用析构函数:总结得非常快,而不是将obj复制/移动到bar然后销毁objobj直接构造在bar的存储中。

bar.a = 5;

这里没什么好说的。我们只是将bar.a的值更改为 5。

bar = newObj();

这里bar被复制赋值1的返回值newObj(),那么这个函数调用创建的临时对象被破坏2,这是第一个Goodbye。此时bar.a不再是5而是临时对象a中的任何内容。

}

main()结束时,局部变量被破坏,包括bar,这是第二个Goodbye,由于之前的赋值而没有打印5


1由于用户定义的析构函数,此处不会发生移动赋值,也不会隐式声明移动赋值运算符。
2正如 YSC 在评论中提到的,请注意,此析构函数调用具有未定义的行为,因为它正在访问此时未初始化的a。出于同样的原因,将bar与临时对象一起分配,特别是将a作为临时对象的一部分进行分配,也具有未定义的行为。

1)很简单,代码中有两个Foo对象(mainnewObj),所以有两个析构函数调用。实际上,这是您将看到的析构函数调用的最小数量,编译器可能会为返回值创建一个未命名的临时对象,如果这样做了,您将看到三个析构函数调用。在C++的历史记录中,返回值优化的规则已发生变化,因此您可能会也可能看不到此行为。

2)因为当析构函数被调用时,Foo::a的值永远不会是5,它在newObj中永远不会是5,虽然它在main是5,但当你到达main结束时(也就是调用析构函数的时候)。

我猜你的误解是你认为赋值语句bar = newObj();应该调用析构函数,但事实并非如此。在分配期间,对象被覆盖,它不会被销毁。

我认为这里的主要混淆之一是对象标识。

bar始终是同一个对象。当您将不同的对象分配给bar时,您不会销毁第一个对象 - 您调用operator=(const& Foo)(复制赋值运算符)。它是编译器可以自动生成的五个特殊成员函数之一(在这种情况下是这样),并且只是用newObj().a中的任何内容覆盖bar.a。提供您自己的operator=以查看/何时发生这种情况(并确认a确实5发生这种情况)。

bar的析构函数只被调用一次 - 当bar在函数结束时超出范围时。只有一个其他析构函数调用 - 用于第二个newObj()返回的临时。从newObj()中的第一个临时被省略(语言在这种情况下允许它,因为从来没有真正意义上的创建和立即销毁它)并使用返回值newObj()直接初始化bar