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

Destructors on classes returned by functions

我有以下代码:

#include <stdio.h>

class Foo {
    public:
    int a;
    ~Foo() { printf("Goodbye %d\n", 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,我无法理解析构函数,它们何时被调用以及如何从函数返回 class。

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

2) 因为在调用析构函数时 Foo::a 的值永远不会是 5,所以它在 newObj 中永远不会是 5,尽管在 main 中它是 5 但它不是当你到达 main 的末尾时(即调用析构函数时)。

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

让我们看看你的主要功能发生了什么:

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

这里我们只是实例化一个Foo并用newObj()的return值初始化它。这里没有调用析构函数是因为copy elision:总结的很快,不是把copying/movingobj变成bar然后析构objobj直接在bar的存储中构建。

    bar.a = 5;

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

    bar = newObj();

这里bar是copy-assigned1newObj()的returned值,那么这个创建的临时对象函数调用被析构2,这是第一个Goodbye。此时 bar.a 不再是 5 而是临时对象的 a.

中的任何内容
}

main()结束,局部变量被析构,包括bar,这是第二个Goodbye,因为之前赋值,所以没有打印5


1 由于 user-defined 析构函数,这里没有移动赋值,没有隐式声明移动赋值运算符。
2 正如 YSC 在评论中提到的,请注意此析构函数调用具有未定义的行为,因为它正在访问此时未初始化的 abar 与临时对象的赋值,特别是作为其中一部分的 a 的赋值,出于相同的原因也具有未定义的行为。

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

bar 总是 同一个对象。当您将不同的对象分配给 bar 时,您不会破坏第一个 - 您调用 operator=(const& Foo)(复制赋值运算符)。它是编译器可以 auto-generated 的 five special member functions 之一(在这种情况下是这样),只是 覆盖 bar.a 任何东西在 newObj().a。提供您自己的 operator= 以查看 that/when 发生这种情况(并确认 a 确实是 5 发生之前)。

bar 的析构函数仅被调用一次 - 当 bar 在函数末尾超出范围时。只有一个其他析构函数调用 - 由第二个 newObj() 编辑的临时 return。来自 newObj() 的第一个临时变量被省略(在这种情况下语言允许它,因为创建和立即销毁它从来没有真正的意义)并直接用 return 值初始化 bar newObj().