首页 > 解决方案 > c++ - 在赋值时调用析构函数

问题描述

我仍在学习 C++ 的基础知识,所以我可能没有正确的词汇来找到我的问题的答案,但我在任何地方都找不到提到的这个。

如果我有一个带有构造函数和析构函数的类,为什么在分配给类时会在新数据上调用析构函数?

例如:

#include <iostream>

class TestClass {
    public:
    int* some_data;

    TestClass() {
        std::cout << "Creating" << std::endl;
        some_data = (int*)malloc(10*sizeof(int));
    }

    ~TestClass() {
        std::cout << "Deconstructing" << std::endl;
        free(some_data);
    }

    TestClass(const TestClass& t) : some_data{t.some_data} {
        std::cout << "Copy" << std::endl;
    }
};

int main() {
    TestClass foo;
    std::cout << "Created once" << std::endl;
    foo = TestClass();
    std::cout << "Created twice" << std::endl;
}

打印:

Creating
Created once
Creating
Deconstructing
Created twice
Deconstructing
free(): double free detected in tcache 2
Aborted (core dumped)

因此,在调试器中执行此操作后,似乎在新创建的数据上调用了解构器,这让我感到困惑。不应该释放一次原始数据,然后在执行结束时释放新数据吗?似乎原始数据永远不会像这样被释放。

标签: c++

解决方案


您的对象拥有一个指向已分配内存的原始指针,但没有实现适当的复制构造函数来进行分配并复制指针后面的数据。正如所写,当您复制一个对象时,指针被复制,这样现在两个对象指向同一个地址(以及刚刚分配给对象的旧地址被泄露。)

当临时对象超出范围时,它会删除其指针,但副本 (foo) 仍指向它。当 foo 超出范围时,它会再次删除相同的指针,从而导致您看到这个双重释放错误。

如果你需要编写析构函数来清理,你几乎总是需要同时提供复制和赋值操作,或者禁用它们。

建议:

  • 将指针保存在 astd::unique_ptr中,如果您尝试复制它,它将无法编译。这迫使你处理这个问题。此外,malloc 和 free 主要用于 C 或低级 C++ 内存管理。考虑改为使用 new 和 delete 进行分配。(unique_ptrdelete默认使用, not free,你不能混合它们。)
  • 或者,删除复制构造函数和赋值运算符
  • 另外,考虑当您想从 xvalue(临时或移动的左值)移动时,您可以从右侧窃取分配。所以这个类是移动构造函数和移动赋值的一个很好的候选。

推荐阅读