Подтвердить что ты не робот

Двойное освобождение или повреждение после очереди :: push

#include <queue>
using namespace std;

class Test{
    int *myArray;

        public:
    Test(){
        myArray = new int[10];
    }

    ~Test(){
        delete[] myArray;
    }

};


int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

После запуска этого процесса я получаю ошибку времени выполнения "двойная свобода или повреждение". Если я избавлюсь от содержимого деструктора (delete), он отлично работает. Что не так?

4b9b3361

Ответ 1

Расскажите о копировании объектов в С++.

Test t;, вызывает конструктор по умолчанию, который выделяет новый массив целых чисел. Это прекрасно, и ваше ожидаемое поведение.

Проблема возникает, когда вы нажимаете t в свою очередь, используя q.push(t). Если вы знакомы с Java, С# или почти любым другим объектно-ориентированным языком, вы можете ожидать, что объект, который вы создали, был добавлен в очередь, но С++ не работает таким образом.

Когда мы рассмотрим std::queue::push method, мы видим, что элемент, который добавляется в очередь, "инициализируется копией х". Это действительно новый объект, который использует конструктор копирования для дублирования каждого члена вашего исходного объекта Test, чтобы создать новый Test.

Ваш компилятор С++ создает для вас конструктор копирования по умолчанию! Это очень удобно, но вызывает проблемы с элементами указателя. В вашем примере помните, что int *myArray - это просто адрес памяти; когда значение myArray копируется из старого объекта в новое, теперь у вас есть два объекта, указывающих на один и тот же массив в памяти. Это не по своей сути плохо, но деструктор попытается дважды удалить один и тот же массив, следовательно, ошибка времени выполнения "двойной свободной или поврежденной".

Как его исправить?

Первым шагом является реализация конструктора копирования, который может безопасно копировать данные с одного объекта на другой. Для простоты он может выглядеть примерно так:

Test(const Test& other){
    myArray = new int[10];
    memcpy( myArray, other.myArray, 10 );
}

Теперь, когда вы копируете объекты Test, для нового объекта будет назначен новый массив, а также будут скопированы значения массива.

Мы все еще не совсем обеспокоены. Существует еще один метод, который генерирует компилятор для вас, что может привести к аналогичным проблемам - присвоение. Разница в том, что при назначении у нас уже есть существующий объект, память которого должна управляться надлежащим образом. Здесь выполняется реализация базового оператора присваивания:

Test& operator= (const Test& other){
    if (this != &other) {
        memcpy( myArray, other.myArray, 10 );
    }
    return *this;
}

Важная часть здесь заключается в том, что мы копируем данные из другого массива в этот массив объектов, сохраняя каждую память объекта отдельно. У нас также есть чек для самостоятельного назначения; в противном случае мы будем копировать от себя к себе, что может вызвать ошибку (не уверен, что она должна делать). Если мы удаляем и выделяем больше памяти, проверка самоназначения не позволяет нам удалять память, из которой мы должны копировать.

Ответ 2

Проблема заключается в том, что ваш класс содержит управляемый указатель RAW, но не реализует правило из трех (пять в С++ 11). В результате вы получаете (предположительно) двойное удаление из-за копирования.

Если вы учитесь, вы должны научиться реализовать правило из трех (пяти). Но это не правильное решение этой проблемы. Вы должны использовать стандартные объекты контейнера, а не пытаться управлять собственным внутренним контейнером. Точный контейнер будет зависеть от того, что вы пытаетесь сделать, но std::vector является хорошим значением по умолчанию (и вы можете изменить пароль, если он не является opimal).

#include <queue>
#include <vector>

class Test{
    std::vector<int> myArray;

    public:
    Test(): myArray(10){
    }    
};

int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

Причиной использования стандартного контейнера является separation of concerns. Ваш класс должен быть связан либо с бизнес-логикой, либо с управлением ресурсами (не для обоих). Предполагая, что Test - это некоторый класс, который вы используете для поддержания определенного состояния вашей программы, тогда это бизнес-логика, и он не должен заниматься управлением ресурсами. Если, с другой стороны, Test должен управлять массивом, вам, вероятно, нужно узнать больше о том, что доступно в стандартной библиотеке.

Ответ 3

Вы получаете double free или, потому что первый деструктор для объекта q, в этом случае память, выделенная новой, будет бесплатной. В следующий раз, когда detructor будет вызван для объекта t, в это время память уже свободна (выполняется для q), поэтому, когда в деструкторе удалить [] myArray; будет выполняться, он будет throw двойной свободный или коррупционный. Причина в том, что оба объекта, разделяющие одну и ту же память, определяют \copy, присваивание и равный оператор, как указано в приведенном выше ответе.

Ответ 4

Вам нужно определить конструктор копирования, назначение, оператор.

class Test {
   Test(const Test &that); //Copy constructor
   Test& operator= (const Test &rhs); //assignment operator
}

Ваша копия, помещенная в очередь, указывает на ту же память, что и ваш оригинал. Когда первый разрушается, он удаляет память. Второй разрушает и пытается удалить одну и ту же память.

Ответ 5

Вы также можете попробовать проверить null до удаления, чтобы

if(myArray) { delete[] myArray; myArray = NULL; }

или вы можете определить все операции удаления безопасным образом следующим образом:

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
#endif

#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
#endif

а затем используйте

SAFE_DELETE_ARRAY(myArray);

Ответ 6

Um, не следует, чтобы деструктор вызывал delete, а не удалял []?