为什么自动对象的析构函数被调用两次?

Why is automatic object's destructor called twice?

(我的问题的答案涉及复制构造函数,但复制发生在函数 return 上,而不是在对另一个 class 的方法调用中。我实际上看到了引用的可能重复项, 但没有从 vector::push_back 制作的副本中推断出我这里的函数也制作了副本。也许我应该这样做。)

我正在尝试了解自动对象的 construction/destruction。我 运行 进入了一些对我来说可疑的代码,所以我编写了自己的版本以努力理解它。简而言之,原始代码包含一个函数,该函数 returned 一个函数本地对象(自动)。这对我来说看起来不安全,所以我编写了这个程序来探索它:

#include <stdio.h>

class Phantom
{
private:
    static int counter;
    int id;

public:
    Phantom()
    {
        ++counter;
        id = counter;
        printf("Phantom %d constructed.\n", id);
    };

    virtual ~Phantom()
    {
        printf("Phantom %d destructed.\n", id);
    };

    void speak()
    {
        printf("Phantom %d speaks.\n", id);
    };
};

int Phantom::counter = 0;

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

int main()
{
    Phantom phantom;

    phantom = getPhantom();

    phantom.speak();

    return 0;
}

我得到这个输出:

Phantom 1 constructed.
Phantom 2 constructed.
Phantom 2 destructed.
Phantom 2 destructed.
Phantom 2 speaks.

输出中的第四行让我感到困惑。

当输入main时,Phantom 1会自动构建。

幻影2在输入getPhantom时自动生成

Phantom 2 在 getPhantom 退出时自动销毁(这就是为什么我认为 return 从 getPhantom 中调用它是不安全的)。

但在那之后我很困惑。根据调试器,getPhantom 在 第四行输出出现之前 return 编辑了 。第二次调用Phantom的析构函数时,调用栈是这样的:

main
~Phantom

在托管语言中,我可以看到这一行:

phantom = getPhantom();

会破坏 Phantom 1,但不会触及 Phantom 2。这是 C++,而不是 Java。

什么导致第二次调用 Phantom 2 的析构函数?

你return一份。因此,getPhantom() 中的变量在范围的末尾被销毁,你留下它的副本也有 id 2。这是因为在 return 它调用复制构造函数(也是默认的)不增加 ID。

您忘记正确说明:

  1. 复制构造函数。

  2. 赋值运算符。

在这两种情况下,您最终会得到多个具有相同 id 的对象,并且两个对象最终都在其析构函数中打印相同的 id。在复制构造函数的情况下,构造函数中不会打印任何消息,因为您没有定义自己的复制构造函数。在赋值运算符的情况下,构造函数中分配的 id 会被另一个对象的重复 id 覆盖。这是这里发生的事情:

phantom = getPhantom();

因此,您的会计处理有误。

与其质疑如此简单的代码是否会导致破坏一个从未构造过的对象,或者破坏某个东西两次,不如考虑更可能的是对象 构造的并且每个对象只被摧毁一次,但你没有准确追踪建造和破坏。

现在考虑在 C++ 中构造对象的其他方式,并考虑如果在任何时候使用复制构造函数会发生什么。然后考虑如何从函数中 return 本地对象,以及是否使用复制构造函数。

如果您想改进您的测试代码,请在析构函数中打印出 this 指针的值,您会发现您尝试为每个对象指定一个 ID 是有缺陷的。您有多个具有不同身份(即内存中的地址)但相同 "ID".

的对象

Phantom autoPhantom;

return autoPhantom; // THIS CAN'T BE SAFE

绝对安全。该函数按值返回对象,也就是说,将制作并返回一个副本(可能被 "return value optimization" (RVO) 省略)。

如果函数返回了局部变量的引用或指针,那么你是对的,而且它是不安全的。

调用"extra"析构函数的原因很简单,局部变量被销毁,然后返回的副本被销毁。

我会评论您对 return 具有自动存储的对象不安全的担忧:

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

如果那不安全,那么 C++ 将毫无用处,您不觉得吗?要了解我在说什么,只需将类型替换为...比如 int:

int getPhantom()
{
    int autoPhantom = 0;

    return autoPhantom; // How does this look to you now?
}

明确一点:它是绝对安全的,因为您正在 return 获取值(即对象的副本)。

对return这样一个对象的指针或引用是不安全的:

int* getInt()
{
   int a = 0;
   return &a;
}

将这样的代码添加到您的 class:

Phantom& operator=(const Phantom& inPhantom)
{
    printf("Assigning.\n");
}

你会看到第二个对象没有被销毁两次。解释更简单。在赋值操作中,第一个对象将其所有字段值更改为第二个对象的值,但不会被销毁。它仍然是第一个对象。 您更新的示例:http://cpp.sh/6b4lo