为什么自动对象的析构函数被调用两次?
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。
您忘记正确说明:
复制构造函数。
赋值运算符。
在这两种情况下,您最终会得到多个具有相同 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
(我的问题的答案涉及复制构造函数,但复制发生在函数 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。
您忘记正确说明:
复制构造函数。
赋值运算符。
在这两种情况下,您最终会得到多个具有相同 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