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

时间:2016-07-20 12:39:13

标签: c++ destructor automatic-storage

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

我试图理解自动对象的构造/破坏。我遇到了一些看起来很可疑的代码,所以我编写了自己的版本以努力理解它。简而言之,原始代码包含一个返回函数本地对象的函数(自动)。这对我来说看起来不安全,所以我写了这个程序来探索它:

n

我得到了这个输出:

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

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

在输入#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; } 时自动构建幻像1。

在输入main时自动构建幻像2。

当退出getPhantom时,幻像2会被自动销毁(这就是为什么我认为从getPhantom返回它是不安全的。)

但在那之后我很困惑。根据调试器,getPhantom在输出第四行之前返回。当第二次调用getPhantom的析构函数时,调用堆栈是:

main
~Phantom

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

Phantom

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

第二次调用Phantom 2的析构函数是什么原因?

6 个答案:

答案 0 :(得分:7)

您返回一份副本。因此,getPhantom()中的变量在作用域的末尾被销毁,并且你的副本也是id 2。这是因为在返回时它调用了不增加id的复制构造函数(也是默认的)

答案 1 :(得分:5)

您忘记了正确说明:

  1. 复制构造函数。

  2. 作业运营商。

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

    phantom = getPhantom();
    

    因此,您的会计错误。

答案 2 :(得分:3)

我将评论您对使用自动存储返回对象不安全的担忧:

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?
}

要清楚:它是完全安全的,因为你正在返回值(即对象的副本)。

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

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

答案 3 :(得分:2)

不要质疑这样的简单代码是否导致破坏一个从未构造过的对象,或者两次破坏某些东西,而是考虑到构造 对象的可能性更大,而且每个对象都是只销毁一次,但你没有准确地跟踪建筑和破坏。

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

如果要改进测试代码,请在析构函数中打印出this指针的值,并且您将看到您尝试为每个对象提供ID是有缺陷的。您有多个具有不同身份的对象(即内存中的地址),但相同的&#34; ID&#34;。

答案 4 :(得分:2)

  

Phantom autoPhantom;

     

返回autoPhantom; //这可以安然

非常安全。该函数按值返回对象,即将生成并返回一个副本(可能由&#34;返回值优化&#34;(RVO))删除。

如果函数返回了一个引用或指向局部变量的指针,那么你就是对的,这将是不安全的。

&#34;额外&#34;析构函数调用只是破坏局部变量,然后销毁返回的副本。

答案 5 :(得分:1)

将此类代码添加到您的班级:

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

并且您将看到第二个对象未被销毁两次。这种探索更为简单。在赋值操作时,第一个对象将其所有字段值更改为第二个对象的值,但不会将其销毁。它仍然是第一对象。 您更新的示例:http://cpp.sh/6b4lo