【问题标题】:c++ - Destructor called on assignmentc++ - 在赋值时调用析构函数
【发布时间】:2021-10-01 01:56:51
【问题描述】:

我仍在学习 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)

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

【问题讨论】:

  • 词汇:“析构函数”而不是“解构函数”
  • 还有一个非常重要的词汇:rule of 3/5
  • 您的复制构造函数已损坏,您最终会得到 2 个具有指向相同 some_data 的指针的对象。此外,您还缺少一个赋值运算符重载(这是 foo = TestClass(); 调用的)

标签: c++


【解决方案1】:

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

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

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

建议:

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

【讨论】:

  • 不仅缺少赋值,op 确实有复制构造函数,但它没有做正确的事情
  • 我从未检查过复制构造函数,因为它没有被调用,输出永远不会从它打印出来。但是赋值运算符的问题听起来是正确的
  • 移动作业正是我所需要的
【解决方案2】:

大部分的 cmets 和一些代码中的更多细节:

#include <iostream>
#include <array>
#include <memory>

class TestClass 
{
// members of a class should not be public
private:

    // TestClass owns the data, this is best modeled 
    // with a unique_ptr. std::array is a nicer way of
    // working with arrays as objects (with size!)
    std::unique_ptr<std::array<int, 10>> some_data;

public:
    TestClass() :
        some_data{ std::make_unique<std::array<int,10>>() }
    {
        std::cout << "Creating" << std::endl;

        // set everything in the array to 0
        std::fill(some_data->begin(), some_data->end(), 0);
    }

    ~TestClass() 
    {
        std::cout << "Destructing" << std::endl;
        // no need to manually delete a std::unique_ptr
        // its destructor will free the memory
        // and that will be called as part of this destructor
    }

    TestClass(const TestClass& t) : 
        // when you copy a class the copy should have its
        // own copy of the data (to avoid deleting some data twice)
        // or you must chose shared ownership (lookup std::shared_ptr)
        some_data{ std::make_unique<std::array<int,10>>() }
    {
        std::cout << "Copy" << std::endl;

        // copy data from t to this instances array
        // (note this would not have been necessary
        // it your array was just a non-pointer member,
        // try that for yourself too.)
        std::copy(some_data->begin(), some_data->end(), t.some_data->begin());
    }

    TestClass(TestClass&& rhs) :
        some_data{ std::move(rhs.some_data) } // transfer ownership of the unique_pointer to this copy
    {
        std::cout << "Move" << std::endl;
    }

    // Important this assignement operator is used in your original code too
    // but you couldn't see it!
    TestClass& operator=(const TestClass& t)
    {
        some_data = std::make_unique<std::array<int, 10>>();
        std::copy(some_data->begin(), some_data->end(), t.some_data->begin());

        std::cout << "Assignment" << std::endl;
        return *this;
    }

    

};

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

    foo = TestClass();
    std::cout << "Created twice" << std::endl;

    TestClass bar{ std::move(foo) };
    std::cout << "Moved" << std::endl;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-18
    • 1970-01-01
    • 2012-04-01
    • 2020-10-29
    • 1970-01-01
    • 2019-11-16
    • 1970-01-01
    • 2014-11-27
    相关资源
    最近更新 更多