Двойное освобождение или повреждение после очереди :: 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
), он отлично работает. Что не так?
Ответы
Ответ 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, а не удалял []?