为什么自动对象的析构函数被调用两次?
Why is automatic object's destructor called twice?
我正在努力理解自动物体的构造/破坏。我遇到了一些在我看来可疑的代码,所以我写了自己的版本来理解它。简而言之,原始代码包括一个函数,该函数返回了该函数的本地对象(一个自动对象)。这对我来说似乎不安全,所以我写了这个程序来探索它:
#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构建。幻影2已构建。幻影2已销毁。幻影2已销毁。幻影2说话
让我困惑的是输出中的第四行
当输入main
时,幻影1会自动构建。
当输入getPhantom
时,幻影2会自动构建。
当getPhantom
退出时,Phantom 2会自动销毁(这就是为什么我认为从getPhantom
返回它是不安全的)。
但在那之后,我感到困惑。根据调试器的说法,getPhantom
在第四行输出出现之前已经返回。当第二次调用Phantom
的析构函数时,调用堆栈如下:
main~幻影
在一种管理语言中,我可以看到这句话:
phantom = getPhantom();
会摧毁幻影1,但不会触及幻影2。这是C++,而不是Java。
是什么导致第二次调用幻影2的析构函数?
返回一份副本。因此,getPhantom()
中的变量在作用域的末尾被销毁,剩下的是其id为2的副本。这是因为在返回时,它调用复制构造函数(也是默认构造函数),而复制构造函数不会增加id。
您忘记了正确解释:
-
复制构造函数。
-
赋值运算符。
在这两种情况下,您最终都会得到多个具有相同id
的对象,两个对象最终都会在其析构函数中打印相同的id
。在复制构造函数的情况下,构造函数中不会打印任何消息,因为您没有定义自己的复制构造函数。在赋值运算符的情况下,构造函数中分配的id
会被另一个对象的重复id
覆盖。这就是这里发生的事情:
phantom = getPhantom();
因此,您的会计核算会出错。
我将评论您对返回具有自动存储的对象不安全的担忧:
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;
}
幻影autoPhantom;
return autoPhantom;//这不可能是安全的
它非常安全。函数是按值返回对象,也就是说,将制作并返回一个副本(可能被"返回值优化"(RVO)所忽略)。
如果函数返回了一个指向局部变量的引用或指针,那么你是对的,它是不安全的。
"额外"析构函数调用的原因只是局部变量被销毁,然后返回的副本被销毁。
与其质疑这样简单的代码是否会破坏一个从未构建过的对象,或者两次破坏某个对象,不如考虑对象更有可能是构建过的,每个对象只被破坏一次,但你没有准确地跟踪构建和破坏。
现在想想在C++中构造对象的其他方法,并考虑如果在任何时候使用复制构造函数会发生什么。然后考虑如何从函数返回本地对象,以及是否使用了复制构造函数。
如果你想改进你的测试代码,打印出析构函数中this
指针的值,你会发现你给每个对象一个ID的尝试是有缺陷的。您有多个具有不同标识(即内存中的地址)但具有相同"ID"的对象。
将这样的代码添加到您的类中:
Phantom& operator=(const Phantom& inPhantom)
{
printf("Assigning.n");
}
你会看到第二个物体没有被摧毁两次。解释更为简单。在赋值操作中,第一个对象将其所有字段值更改为第二个对象的值,但它不会被销毁。它仍然是第一个物体。您更新的示例:http://cpp.sh/6b4lo
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- C++析构函数调用两次,堆栈分配的复合对象
- Qt插槽调用了两次
- 对于优化级别为 0 的 std::vector,析构函数被调用两次
- 为什么转换运算符调用复制构造函数两次,而等效函数只调用它一次
- 调用一个小函数两次(例如在if条件和主体中)比将结果存储在局部变量中更可取
- 为什么这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次
- 插槽调用了两次qt
- 调用某个回调函数两次会导致分段错误:Nan
- 基于 MFC 对话框的应用程序无法调用对话框两次
- 重载运算符 new(),为什么构造函数被调用两次?
- 当 reset() 被unique_ptr调用两次时会发生什么?
- 为什么在C 中超载邮政增量运算符两次调用构造函数
- 现代C++编译器是否能够避免在某些条件下两次调用常量函数
- 如果我对async_read进行两次调用,那么只有在处理完第一次调用之后,才会处理第二次调用,这是否安全
- 如何正确地将对象添加到向量,而无需两次调用析构函数
- boost::asio vs. libpcap:避免两次调用关闭
- 为什么 DNSServiceProcessResult 两次调用我的回调
- 在资源管理器左窗格上两次调用Windows 7外壳扩展dll Initialize方法
- 通过连续两次调用boost::asio::read来检索正确的数据