堆栈上的对象被覆盖时不调用析构函数

Posted

技术标签:

【中文标题】堆栈上的对象被覆盖时不调用析构函数【英文标题】:Destructor not called when object on stack is overwritten 【发布时间】:2012-08-17 10:41:35 【问题描述】:

今天我想知道 C++ 的析构函数,所以我写了一个小测试程序。这回答了我原来的问题,但提出了一个新问题: 以下程序:

#include "stdafx.h"
#include <vector>
#include <iostream>
using namespace std;
class test

public:
    int id;
    vector<test> collection;
    test()
    test(int id_in)id = id_in;
    ~test()cout << "dying: " << id << "\n";
;

int _tmain(int argc, _TCHAR* argv[])

    
        test obj(1);
        obj.collection.push_back(test(2));
        obj.collection.push_back(test(3));
        cout << "before overwrite\n";
        obj = test(4);
        cout << "before scope exit\n";
    
    int x;
    cin >> x;

产生以下输出:

dying: 2
dying: 2
dying: 3
before overwrite
dying: 2
dying: 3
dying: 4
before scope exit
dying: 4

为什么我没有看到 id 为 1 的测试对象的析构函数?如果它的析构函数在被覆盖时没有被调用,那么它的向量中的实例的析构函数是什么?

【问题讨论】:

【参考方案1】:

您通过创建析构函数但没有赋值运算符违反了Rule of Three。

通过阅读,您可以如下解释您的代码:

当行

obj = test(4);

编译后,会创建一个 ID 为 4 的 test 临时实例。

然后,调用赋值运算符。由于您没有提供,因此编译器为您生成了一个,如下所示:

test& operator=(const test& other)

    id = other.id;
    collection = other.collection;
    return *this;

id 1 简单地被临时的 4 覆盖,对于集合赋值,调用std::vector 的赋值运算符。

std::vector 的赋值运算符删除了所有之前包含的元素,这就是你看到的原因

dying: 2
dying: 3

在您的输出中。最后删除了临时创建的id为4的obj实例,导致

dying: 4

第一次出现。当obj 超出范围时,您会看到

dying: 4

再次输出。

【讨论】:

@Component 10 在阅读了整个事情之后这个问题应该自己回答:-) 确实如此。这有点迂回,但鉴于 OP 试图了解的内容,值得一读 - 公平点。 谢谢您的解释【参考方案2】:

那是因为您没有实现赋值运算符,而是进行了成员赋值。所以这一行:

obj = test(4);

导致第一个对象 (test obj(1)) 中的 id 被覆盖为 4。最后一行dying: 4 来自于销毁那个对象。

【讨论】:

这是我的第一个想法。但如果是这样的话,那么在赋值时为什么以及如何调用对象 2 和 3(obj 集合中 id 为 1 的实例)的析构函数? @Hari:那些对象被创建、复制然后被销毁,并且这个对象被分配给 前三个析构函数调用绝对是这种情况。但对我来说,重新分配 obj 1 似乎会以某种方式触发其向量的析构函数,因此会破坏 obj 2 和 3(输出中的第四个和第五个析构函数调用),如果是这样的话,我想知道向量是如何被破坏的,如果不是这样,我仍然不明白发生了什么。 @Hari:vector 内部也被重新分配,其内容被擦除,这是第 4 和第 5 个输出的来源。 这就解释了一切。谢谢你的回答。【参考方案3】:

obj 在您执行此操作时不会被破坏:

obj = test(4);

所发生的一切是 test(4) 正在创建并分配给现有对象,因此 1 的 id 将被 4 覆盖,这就是为什么您将最后一个视为:

dying: 4

【讨论】:

【参考方案4】:

你看不到 1 因为你在最后破坏了 obj 。在你用 test(4) 重写它之前。因此 1 被 4 重写。

【讨论】:

【参考方案5】:

关于实践中的行为,声明

    obj = test(4);

id 成员的值更改为4。因此,当该对象被销毁时,它会报告 ID 为 4 的对象已被销毁。由于您尚未定义复制赋值运算符,因此该赋值执行了成员赋值。

关于正式保证的行为,如果代码中的非标准 "stdafx.h" 标头定义了宏 _tmain_TCHAR预处理产生标准要求的标准main 函数:

C++11 §3.6.1/1: “一个程序应包含一个名为main的全局函数,它是程序的指定开始。它 是实现定义的,是否需要独立环境中的程序来定义主函数。”

虽然不太可能,但这意味着如果标头没有适当地定义了这些宏,那么原则上您可以获得您看到的输出而不管剩下的代码

确保不发生未定义行为的一种方法是简单地使用标准main

毕竟,截至 2012 年,使用那些旨在支持 Windows 9x 的 Microsoft 宏绝对没有任何优势,尤其是考虑到 Microsoft 使用 Unicode 层已经使这些宏过时 2001 年。

也就是说,在 10 年后继续使用它们,只是毫无意义的混淆和额外的工作,包括你不能正式地说你的程序必须产生任何特定的结果。

【讨论】:

以上是关于堆栈上的对象被覆盖时不调用析构函数的主要内容,如果未能解决你的问题,请参考以下文章

为啥在调用它的析构函数后我可以访问这个堆栈分配的对象? [复制]

C++ 堆栈分配对象,显式析构函数调用

为啥在析构函数中抛出异常时不调用重载删除?

为啥C++里面,析构函数会被调用两次

静态对象成员会在所属类的析构函数被调用时自动析构吗?

c++ 析构函数 是在啥时候执行