Doble gratis o corrupción … pero ¿por qué?

#include  using namespace std; class Test{ int *myArray; public: Test(){ myArray = new int[10]; } ~Test(){ delete[] myArray; } }; int main(){ queue q Test t; q.push(t); } 

Después de ejecutar esto, recibo un error de tiempo de ejecución “double free or corruption”. Si me deshago del contenido del destructor (la delete ), funciona bien. ¿Qué pasa?

Hablemos de copiar objetos en C ++.

Test t; , llama al constructor predeterminado, que asigna una nueva matriz de enteros. Esto está bien, y tu comportamiento esperado.

El problema surge cuando presionas t en tu cola usando q.push(t) . Si está familiarizado con Java, C # o casi cualquier otro lenguaje orientado a objetos, es de esperar que el objeto que creó earler se agregue a la cola, pero C ++ no funciona de esa manera.

Cuando echamos un vistazo al método std::queue::push , vemos que el elemento que se agrega a la cola se “inicializa en una copia de x”. En realidad, es un objeto completamente nuevo que utiliza el constructor de copias para duplicar cada miembro de su objeto Test original para realizar una nueva Test .

Su comstackdor C ++ genera un constructor de copia para usted de manera predeterminada. Eso es bastante útil, pero causa problemas con los miembros del puntero. En su ejemplo, recuerde que int *myArray es solo una dirección de memoria; cuando se copia el valor de myArray del objeto antiguo al nuevo, ahora tendrá dos objetos apuntando a la misma matriz en la memoria. Esto no es intrínsecamente malo, pero el destructor intentará eliminar la misma matriz dos veces, de ahí el error de tiempo de ejecución “double free or corruption”.

¿Cómo lo arreglo?

El primer paso es implementar un constructor de copia , que puede copiar de forma segura los datos de un objeto a otro. Por simplicidad, podría verse más o menos así:

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

Ahora cuando está copiando objetos de prueba, se asignará una nueva matriz para el nuevo objeto, y los valores de la matriz también se copiarán.

Sin embargo, no estamos completamente fuera de problemas. Hay otro método que el comstackdor genera para usted que podría generar problemas similares: asignación. La diferencia es que con la asignación, ya tenemos un objeto existente cuya memoria necesita ser administrada apropiadamente. Aquí hay una implementación básica del operador de asignación:

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

La parte importante aquí es que estamos copiando los datos de la otra matriz en la matriz de este objeto, manteniendo la memoria de cada objeto por separado. También tenemos un cheque para la autoasignación; de lo contrario, estaríamos copiando de nosotros mismos a nosotros mismos, lo que puede arrojar un error (no estoy seguro de lo que se supone que debe hacer). Si elimináramos y asignaramos más memoria, la comprobación de autoasignación nos impide borrar la memoria de la que debemos copiar.

El problema es que su clase contiene un puntero RAW administrado pero no implementa la regla de tres (cinco en C ++ 11). Como resultado, recibirá (esperable) una eliminación doble debido a la copia.

Si estás aprendiendo, debes aprender cómo implementar la regla de tres (cinco) . Pero esa no es la solución correcta para este problema. Debería utilizar objetos contenedores estándar en lugar de tratar de administrar su propio contenedor interno. El contenedor exacto dependerá de lo que intente hacer, pero std :: vector es un buen valor predeterminado (y puede cambiar los términos posteriores si no es óptimo).

 #include  #include  class Test{ std::vector myArray; public: Test(): myArray(10){ } }; int main(){ queue q Test t; q.push(t); } 

La razón por la que debe usar un contenedor estándar es la separation of concerns . Su clase debe preocuparse por la lógica comercial o la administración de recursos (no ambas). Asumiendo que la Test es una clase que está utilizando para mantener algún estado sobre su progtwig, entonces es lógica de negocios y no debería estar haciendo la administración de recursos. Si, por otro lado Test se supone que Test debe administrar una matriz, probablemente necesite aprender más sobre lo que está disponible dentro de la biblioteca estándar.

Está obteniendo doble libre o corrupción porque el primer destructor es para el objeto q en este caso la memoria asignada por nuevo será libre. La próxima vez que se llame al detructor para el objeto t en ese momento la memoria ya esté libre (hecho para q) cuando en destructor elimine [] myArray; ejecutará lanzará doble gratis o corrupción . El motivo es que ambos objetos comparten la misma memoria, por lo que definen \ copy, assignment y equal operator como se menciona en la respuesta anterior.

Necesita definir un constructor de copia, asignación, operador.

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

Su copia que está presionada en la cola apunta a la misma memoria que su original. Cuando el primero es destruido, borra la memoria. El segundo destruye e intenta eliminar la misma memoria.

También puede intentar verificar nulo antes de eliminarlo de tal manera que

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

o puede definir todas las operaciones de eliminación de una manera segura como esta:

 #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 

y luego usa

 SAFE_DELETE_ARRAY(myArray); 

Um, ¿no debería el destructor llamar a eliminar, en lugar de eliminar []?