C++ class 未自动调用析构函数导致内存泄漏?

C++ class destructor not being called automatically causes memory leak?

(之前有人问:没有,我没忘记delete[]语句)

我正在摆弄动态分配的内存,我 运行 陷入了这个问题。我认为解释它的最好方法是向您展示我编写的这两段代码。它们非常相似,但其中之一没有调用我的 class 析构函数。

// memleak.cpp
#include <vector>

using namespace std;

class Leak {
    vector<int*> list;

  public:
    void init() {
        for (int i = 0; i < 10; i++) {
            list.push_back(new int[2] {i, i});
        }
    }

    Leak() = default;
    ~Leak() {
        for (auto &i : list) {
            delete[] i;     
        }
    }
};

int main() {
    Leak leak;
    while (true) {
        // I tried explicitly calling the destructor as well,
        // but this somehow causes the same memory to be deleted twice
        // and segfaults
        // leak.~Leak();
        leak = Leak();
        leak.init();
    }
}

// noleak.cpp
#include <vector>

using namespace std;

class Leak {
    vector<int*> list;

  public:
    Leak() {
        for (int i = 0; i < 10; i++) {
            list.push_back(new int[2] {i, i});
        }
    };
    ~Leak() {
        for (auto &i : list) {
            delete[] i;     
        }
    }
};

int main() {
    Leak leak;
    while (true) {
        leak = Leak();
    } 
}

我用 g++ filename.cpp --std=c++14 && ./a.out 编译了它们,并用 top 检查内存使用情况。

如您所见,唯一的区别是,在 memleak.cpp 中,class 构造函数不执行任何操作,并且有一个 init() 函数执行其工作。但是,如果您尝试这样做,您会发现这会以某种方式干扰正在调用的析构函数并导致内存泄漏。

我是不是遗漏了什么明显的东西?提前致谢。

此外,在有人建议不使用动态分配的内存之前:我知道这并不总是一个好的做法等等,但我现在感兴趣的主要事情是理解为什么我的代码不按预期工作。

您的 class 不尊重 rule of 3/5/0leak = Leak(); 中默认生成的 移动赋值 复制赋值运算符使 leak 引用临时 Leak 对象的内容,它会立即删除在其生命周期结束时,留下 leak 悬空指针,稍后将尝试再次删除。

注意:如果您的 std::vector 的实现在移动时系统地清空了原始向量,但是 that is not guaranteed.

注2:上面删掉的部分是我自己写的,没注意,如 pointed out to me, your class does not generate a move-assignment operator because it has a user-declared destructor。生成并使用复制赋值运算符。

使用智能指针和容器为您的 classes(std::vector<std::array<int, 2>>std::vector<std::vector<int>>std::vector<std::unique_ptr<int[]>>)建模。不要使用 newdelete 和原始拥有指针。在您可能需要它们的极少数情况下,请务必将它们紧密封装并仔细应用上述 3/5/0 规则(包括异常处理)。

这是构造一个临时对象,然后赋值给它。由于您没有编写赋值运算符,因此您得到了一个默认值。默认的只是复制向量 list.

在您的第一个代码中:

  1. 创建一个临时 Leak 对象。它的向量中没有指针。
  2. 将临时对象分配给 leak 对象。这会复制矢量(覆盖旧的)
  3. 删除临时对象,这会删除 0 个指针,因为它的向量是空的。
  4. 分配一堆内存并将指针存储在向量中。
  5. 重复。

在第二个代码中你有:

  1. 创建一个临时 Leak 对象。分配一些内存并将指针存储在其向量中。
  2. 将临时对象分配给 leak 对象。这会复制矢量(覆盖旧的)
  3. 删除临时对象,这将删除临时对象向量中的 10 个指针。
  4. 重复。

请注意,在 leak = Leak(); 之后,临时对象向量中的相同指针也在 leak 的向量中。即使他们被删除了。

要解决这个问题,您应该为您的 class 写一个 operator =rule of 3 是一种记住的方法,如果你有一个析构函数,你 通常 还需要编写一个复制构造函数和复制赋值运算符。 (从 C++11 开始,您还可以选择编写移动构造函数和移动赋值运算符,使其成为 5 的规则)

您的赋值运算符将删除向量中的指针、清除向量、分配新内存以保存正在分配的对象中的 int 值、将这些指针放入向量中并复制 int 值。这样旧的指针就被清理掉了,被赋值给to的对象变成了被赋值对象from的副本,而不共享相同的指针.