繁体   English   中英

尽管定义了复制和移动构造函数,但 emplace_back 和 push_back 给出“双重释放或损坏(fasttop)”错误

[英]emplace_back and push_back give 'double free or corruption (fasttop)' error although copy and move constructor are defined

我只是利用 C++ 并且可能在这里遗漏了一些明显的东西。 我有一个动态分配数组的 class,我想将它的对象放入一个向量中。 由于必须在析构函数中释放数组并且它由vector.push_back()调用,我读到我必须定义一个复制和移动构造函数以避免 memory 损坏错误。 但是,下面的示例仍然给我一个double free or corruption (fasttop)错误。

#include <iostream>
#include <vector>
using namespace std;

class bla {
  public:
    int* arr;

    bla() {
        arr = new int[10];
        for (size_t i = 0; i < 10; i++) {
            arr[i] = 42;
        }
    }

    bla(bla&& b) : arr(b.arr) {
        cout << "move" << endl;
    }

    bla(const bla& b) : arr(b.arr) {
        cout << "copy" << endl;
    }

    ~bla() {
        cout << "delete" << endl;
        delete[] arr;
    }
};

int main() {
    vector<bla> blas;
    blas.reserve(5000);

    blas.push_back(bla());          // same result with emplace_back
    blas.push_back(bla());          // same result with emplace_back
    blas.push_back(bla());          // same result with emplace_back

    return 0;
}

谁能解释我做错了什么,也许还提供一个示例,说明如何将具有动态 memory 的对象添加到向量(我知道我可以使用指针,但从我读到的内容来看,它也应该与对象本身一起使用)。

我用-Wall -std=c++11 -lm运行g++ (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609

更新 1:g++ 7.4.0上运行具有相同标志的相同代码不会导致双重释放错误。 有人知道为什么吗?

更新2:对于后代,因为显然没有那么多最小的例子来实现动态 arrays 浮动的 5 规则。 这就是你应该这样做(如果我错了,请纠正我):

#include <iostream>
#include <vector>
using namespace std;

class bla {
public:
int* arr;

bla()
    : arr(nullptr)
{
    arr = new int[10];
    for (size_t i = 0; i < 10; i++) {
        arr[i] = 42;
    }
}

~bla() {
    cout << "delete" << endl;
    delete[] arr;
}

// deep copy constructor
bla(const bla& other) {
    cout << "copy" << endl;
    arr = new int[10];
    for (size_t i = 0; i < 10; i++) {
        arr[i] = other.arr[i];
    }
}

// deep copy assignment
bla& operator = (const bla& other) {
    cout << "copy assignment" << endl;
    if (&other != this) {
        delete[] arr;
        arr = new int[10];

        for (size_t i = 0; i < 10; i++) {
            arr[i] = other.arr[i];
        }
    }
    return *this;
}

// move constructor
bla(bla&& other)
    : arr(other.arr) {
    cout << "move" << endl;
    other.arr = nullptr;
}

// move assignment
bla& operator = (bla&& other){
    cout << "move assignment" << endl;
    if(&other != this) {
        delete[] arr;
        arr = other.arr;
        other.arr = nullptr;
    }
    return *this;
}
};

int main() {
    vector<bla> blas;

    cout << "=========\nstart pushs\n=========" << endl;

    blas.push_back(bla());
    blas.push_back(bla());
    blas.push_back(bla());

    cout << endl << "=========\nend pushs\n=========" << endl << endl;;

    cout << "for each:" << endl;
    for (bla b : blas) {
        cout << b.arr[0] << endl;
    }

    cout << endl;
    cout << "native for:" << endl;
    for (size_t i = 0; i < 3; i++) {
        cout << blas[i].arr[0] << endl;
    }

    return 0;
}

我读到我必须定义一个复制和移动构造函数以避免 memory 损坏错误。

这是真的,但他们也需要做正确的事。 不仅仅是任何复制/移动构造函数都可以完成这项工作; 而且这里的复制构造函数和移动构造函数都有同样的缺陷。 以复制构造函数为例:

bla(const bla& b) : arr(b.arr) {

所以,如果你用一张纸和铅笔算出这里发生了什么,在复制构造发生后,新的 object 和这个原始的 object, b将具有相同的arr指针。 因为这正是这段代码所做的。

因此,当两个对象都被销毁时,它们的析构函数将尝试delete[]相同的指针。 b和相同的 object 都具有相同的arr 有你的双免费。

移动构造函数也会出现同样的问题。

此外,所示 class 没有显式赋值或移动赋值运算符。 因此,将这些对象中的一个分配给另一个对象将:1)最终泄漏被覆盖对象的arr ,2)最终得到两个具有相同arr和相同问题的对象,当它们最终被销毁时。

在复制构造函数的情况下,除了复制复制的arr及其值之外,您别无选择。 对于移动构造函数,您可以选择将b.arr设置为nullptr 然后析构函数会悄悄地忽略nullptrdelete[]离子 尽管在delete[]之前显式检查nullptr通常被认为是一种好习惯。

您还需要实现适当的operator=重载(只是带有可选移动分配的常规分配),以解决相同的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM