Ejemplos java y C/linux

Tutoriales

Enlaces

Licencia

Creative Commons License
Esta obra está bajo una licencia de Creative Commons.
Para reconocer la autoría debes poner el enlace http://www.chuidiang.com

Trucos de programación de C++

Contenido

(por supuesto, todo esto para unix/linux. En Visual C++ habrá que hacer modificaciones)

Destructores Virtuales.

Cuando construimos una clase padre y de ella heradamos varios hijos con destructor, es aconsejable añadir al padre un destructor virtual, aunque no haga nada.

class Padre
{
   virtual ~Padre() {;}  /* Puede tener o no código */
};

class Hija : public Hija
{
   ~Hija() { /* código */ ; }
};

Si no hacemos esto, podemos encontrar problemas al destruir la clase hija usando un puntero de tipo Padre*. Al no ser el destructor del padre virtual (o no tenerlo), se llamará al destructor de Padre y no al de Hijo

main()
{
   Padre *Puntero = NULL;
   Puntero = new Hija();
   /* ... código ... */
  delete Puntero;       /* Como puntero es de tipo Padre, si su destructor no es virtual, no se llamará al destructor de Hija */
}

La referencia del libro donde encontré este "truquillo" es:

"Programación orientada a objetos con C++"
Fco. Javier Ceballos
Editorial RA-MA

Aconsejo este libro a aquellos que ya saben tienen conocimientos de  C++ y quieren profundizar en detalles de este estilo. Para los que quieren símplemente aprender C++, este mismo autor y en la misma editorial tiene otro libro de aprendizaje.

Llamadas a método virtuales en los destructores

Otra fuente de problemas es llamar desde un destructor de una clase padre a un método virtual que redefine el hijo. Si además el método es virtual puro, el programa se nos caerá con seguridad.
 

class Padre
{
   virtual ~Padre() { metodo(); }
   virtual void metodo() = 0;
};

class Hija
{
   ~Hija() { /* código */;}
   void metodo () { /* más código */ ; }
};

main ()
{
   Hija *Puntero = NULL;
   Puntero = new Hija();
   /* ... código */
   delete Puntero;    /* ¡¡¡ Problema seguro !!! */
}

Cuando se destruye la clase Hija se destruye primero ella misma y luego se destruye (y se llama al destructor de) la clase Padre.

Cuando el destructor de Padre intenta ejecutar metodo(), la clase hija y su metodo() ya han sido destruidos, así que se ejecutará el metodo() de la clase Padre.
En este caso es virtual puro, así que el programa dará un error. Si metodo() sólo fuera virtual, se ejecutaría el de la clase padre, pero no el de la hija en caso de que lo tenga redefinido.

Este error lo he descubierto programando.

Redefinición de new y delete

Los operadores new y delete globales se pueden redefinir, de forma que cada vez que hagamos un new o un delete de cualquier clase o incluso de variables, se llame a nuestro método.
Esto nos puede permitir controlar la memoría que se reserva y libera, para buscar errores del siguiente tipo:

/*
 * El sistema nos pasa el tamaño de memoria que debemos reservar y espera que lo hagamos y devolvamos
 * el puntero a dicha memoria. Las líneas no comentadas son, por tanto, obligadas.
 */

operator new (size_t tamanho)
{
   void *Puntero = NULL;
   Puntero = malloc (tamanho); /* ¡¡ Ojo !!. No llamar a new, porque sería recursivo */
   /* Mi código */
  return Puntero;
}

/*
 * Este operator new[] sólo es necesario redefinirlo en determinados compiladores. Otros compladores
 * llaman directamente al operator new
 */

operatror new[] (size_t tamanho)
{
   void *Puntero = NULL;
   Puntero = malloc (tamanho); /* ¡¡ Ojo !!. No llamar a new, porque sería recursivo */
   /* Mi código */
   return Puntero; /* Obligatorio para que todo siga funcionando correctamente */
}

operator delete (void *puntero)
{
  /* Mi código */
   free (puntero);
}

Para ver si se libera una memoria dos veces o sin reservarla previamente :

Para ver si una memoria se libera dos veces, sería necesario crear un array global void *Punteros[MAX_PUNTEROS]. Los operadores new y new[] crearían nuevas entradas en dicho array. El operador delete buscaría la entrada y la borraría (poniendo un NULL en el array), dando un aviso si no la encuetra. Si en ese aviso se pone un breakpoint de un debugger, es inmediato saber dónde se hace el delete indebido.

Este procedimiento hace que nuestro progama corra más lento: cada vez que se reserva memoria, hay que apuntarlo en el array; cada vez que se libera memoria, hay que recorrer el array para borrar la entrada. Por eso sólo debe utilzarse este método para depurar.

Para ver si no se libera toda la memoria reservada :

Utilizando una variable int global en el código anterior con un contador de memoria reservada (que los operadores new y new[] incrementan y el operador delete decrementa), se puede llevar la contabilidad de memoria reservada y liberada. Posteriormente, con un debugger, se puede ver el valor de dicho contador antes de llamar a algún método sospechoso de no liberar memoria y después de haberlo llamado, confirmando así si libera o no toda la memoria que utiliza.

Una gran ventaja de este método, es que se puede crear un fichero con estos metodos y compilarlo en una librería (por ejemplo, libnew.a) y no es necesario modificar ni recompilar el código que queremos probar (no hay que añadir #include ni reescribir código, únicamente hay que "linkarlo" utilizando o no la librería libnew.a).

Fuentes

Aquí tienes los fuentes new.cc completos de la librería libnew.a que me he creado y utlizo para depurar cuando tengo creciemientos de memoria o sospechas de punteros desmadrados.

Estadísticas y comentarios

Numero de visitas desde el 4 Feb 2007: