[C++][Consulta] Error inesperado con destructor

Iniciado por class_OpenGL, 3 Febrero 2017, 04:28 AM

0 Miembros y 1 Visitante están viendo este tema.

class_OpenGL

Hola, muy buenas. Estoy realizando un árbol binario en C++. Lo estoy encapsulando en una clase. Como cualquier árbol binario, este tiene un dato, un puntero a la izquierda y otro a la derecha. El problema es que al llamar al destructor (el cual lo elimina), el programa finaliza brúscamente. El constructor es el siguiente:

Código (cpp) [Seleccionar]
template <typename T>
class ArbolBinario {
   private:
      T dato;
      ArbolBinario *izda;
      ArbolBinario *dcha;
     
      void Eliminar(ArbolBinario *rama) {
         if(NULL != rama->izda)
            Eliminar(rama->izda);
         
         if(NULL != rama->dcha)
            Eliminar(rama->dcha);
         
         delete rama;
      }
     
   public:
      [...]
     
      ~ArbolBinario() {
         EliminarRamaIzda();
         EliminarRamaDcha();
      }
     
      [...]
     
      void EliminarRamaIzda() {
         if(NULL != izda) {
            Eliminar(izda);
            izda = NULL;
         }
      }
     
      void EliminarRamaDcha() {
         if(NULL != dcha) {
            Eliminar(dcha);
            dcha = NULL;
         }
      }
     
      [...]
};


Este es la función main:

Código (cpp) [Seleccionar]
int main() {
   ArbolBinario<int> arbol;
   
   arbol.SetDato(7, "");
   arbol.ReemplazarRamaIzda(20, "");
   arbol.ReemplazarRamaIzda(5, "0");
   arbol.ReemplazarRamaDcha(7, "00");
   arbol.ReemplazarRamaDcha(4, "0");
   arbol.ReemplazarRamaIzda(1, "01");
   arbol.ReemplazarRamaDcha(2, "01");
   arbol.ReemplazarRamaDcha(8, "");
   arbol.ReemplazarRamaIzda(10, "1");
   
   cout << arbol.ToString();
   
   return 0;
}


Cuando uso el depurador, pongo un punto de ruptura en el destructor, y obtengo lo siguiente:

Breakpoint 1, ArbolBinario<int>::~ArbolBinario (this=0x28fddc, __in_chrg=<optimized out>) at main.cpp:84
84               EliminarRamaIzda();
(gdb) continue
Continuing.

Breakpoint 1, ArbolBinario<int>::~ArbolBinario (this=0x720fb0, __in_chrg=<optimized out>) at main.cpp:84
84               EliminarRamaIzda();
(gdb) continue
Continuing.

Breakpoint 1, ArbolBinario<int>::~ArbolBinario (this=0x7212f0, __in_chrg=<optimized out>) at main.cpp:84
84               EliminarRamaIzda();
(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x004099bb in ArbolBinario<int>::Eliminar (this=0x0, arbol=0x726f58)
    at main.cpp:13
13            void Eliminar(ArbolBinario *arbol) {


Lo que no sé es: ¿por qué se llama 3 veces al constructor? En la última llamada, ¿por qué el puntero this vale 0 (supongo que por esto el programa crashea)?

Muchas gracias

Programador aficionado. Me quiero centrar en programar videojuegos. La API que uso para crearlos es OpenGL

MAFUS

A mi entender duplicas las cosas.
En el destructor vas a borrar las dos ramas por separado pero estas funciones terminan apoyándose en Eliminar, que de igual forma vuelve a borrar a la izquierda y a la derecha por sí misma.
Tal vez debería ser al revés, que Eliminar estuviera a un nivel superior y fuera las que llamara a eliminar izquierda y derecha para borrar así el nodo completo.

De igual forma, tal y como lo tienes, solo EliminarRamaIzda y EliminarRamaDcha se encargan de llevar a NULL el puntero pero Eliminar no lo hace. ¿Llega el programa a un punto en que Eliminar pasará sobre un puntero que ya ha sido borrado previamente?

class_OpenGL

#2
No entiendo muy bien eso de duplico. A ver, tengo el método Eliminar, que te elimina una rama del árbol. Este no comprueba que el árbol que le pases sea nulo (es una precondición que no será así, por así decirlo). Una vez que tengo ese método, creo otros dos que te eliminan la rama izquierda y la rama derecha, las cuales comprueban si la rama es válida (si esta no es nula). Una vez que tengo dos métodos que te eliminan dos ramas (sé que se podría haber resumido en una, pero me pareció útil para mis propósitos) tener métodos que te eliminen ramas independientes. El caso es que el constructor elimina el árbol formado por la rama izquierda y elimina el formado por la de la derecha:

Destructor:
   Elimina rama izquierda.
   Elimina rama derecha

No entiendo dónde está el conflicto.

________________________________________________________

He probado, desde el main, hacer lo del constructor, lo he eliminado y así si que funciona.

Nuevo main:

Código (cpp) [Seleccionar]
int main() {
  ArbolBinario<int> arbol;
 
  arbol.SetDato(7, "");
  arbol.ReemplazarRamaIzda(20, "");
  arbol.ReemplazarRamaIzda(5, "0");
  arbol.ReemplazarRamaDcha(7, "00");
  arbol.ReemplazarRamaDcha(4, "0");
  arbol.ReemplazarRamaIzda(1, "01");
  arbol.ReemplazarRamaDcha(2, "01");
  arbol.ReemplazarRamaDcha(8, "");
  arbol.ReemplazarRamaIzda(10, "1");
 
  cout << arbol.ToString();
 
  arbol.EliminarRamaIzda();
  arbol.EliminarRamaDcha();
 
  return 0;
}


Por lo que alguna condición/requisito especial tiene que tener el destructor.

_____________________________________________________________


He probado a poner cout << "Algo" << endl en el destructor y esto sale:

Algo
Algo
Algo
Algo
Algo
Algo
Algo
Algo
Algo

¿Por qué se llama tantas veces?

______________________________________________________________


Soy nuevo en C++ (vengo de C), ¿delete Objeto llama al destructor del objeto?

Programador aficionado. Me quiero centrar en programar videojuegos. La API que uso para crearlos es OpenGL

MAFUS

#3
Si pudieras pasar tu código entero para probar mejor.

La idea es:
Código (c++) [Seleccionar]

static void Eliminar(ArbolBinario **raiz) {
   // como no soy de C++ no sé si aquí el operador &
   // en el argumento ArbonBinario &rama te podría funcionar
   if(*raiz) {
       delete *raiz;
       *raiz = NULL;
   }
}

void EliminarRamaIzda() {
   if(!izda) {
       delete izda;
       izda = NULL;
   }
}

void EliminarRamaDcha() {
   if(!dcha) {
       delete dcha;
       dcha = NULL;
   }
}

~ArbolBinario() {
   EliminarRamaIzda();
   EliminarRamaDcha();
}


Lo dicho, si tuviera el código completo del árbol podría probarlo, pero por ahora esa es mi idea. Puede tener una infinidad de bugs, código no probado.

ivancea96

Código (cpp) [Seleccionar]
void Eliminar(ArbolBinario *rama) {
   if(NULL != rama->izda)
      Eliminar(rama->izda);

  if(NULL != rama->dcha)
      Eliminar(rama->dcha);

   delete rama;
}


Eliminar no establece las ramas a NULL. COn esta función recursiva, estás eliminando todas las ramas, pero de manera incorrecta:
Primero, liberas la memoria de cada lado de la rama. Luego, llamas al destructor de la rama y la liberas. El destructor de la rama, ya llama a Eliminar. Solo que ahora, llama a Eliminar sobre unas ramas que no apuntan a nada válido.

class_OpenGL

Sii con el gdb (y mucho esfuerzo, por fin me entere del problema. Con C++ hay que tener un cuidado especial, tanto o mas que con C xD (supongo que sera cuestion de muucha practica, como siempre)

Muchas gracias por sus respuestas!!

Programador aficionado. Me quiero centrar en programar videojuegos. La API que uso para crearlos es OpenGL