Poesía Binaria

Destructores virtuales en C++

Muchas veces, cuando leemos código de otras personas, hemos visto destructores declarados como virtual en algunas clases. Es una de esas cosas que si hacemos siempre no pasa nada, pero si no hacemos nunca, en ocasiones nos llevamos sorpresas; pero muchos desarrolladores optan por poner sólo en los casos justos.

¿ Para qué valían los métodos virtual ?

Eso está en un post anterior, pero básicamente valen para que las subclases tengan un método propio que se llame de la misma forma que el método declarado como virtual. Aunque cuando se trata de los destructores… en cada clase tendremos un destructor que se llama como mi clase, por lo que parece que esto no tiene demasiado sentido. Aunque está relacionado.

¡Demostración de la destrucción!

Aunque esto difiera de cómo construiríamos esta estructura en realidad, vamos a imaginarnos que nuestras clases formarán un edificio. Tendremos la clase Edificio, la clase Planta, y la clase Habitación, donde Planta será subclase de Edificio y Habitación subclase de Planta.

Para construir la habitación, todo va bien, primero creamos el Edificio, luego la Planta y finalmente la Habitación; pero para destruirlo todo, depende del tipo de objeto que creemos y, en ocasiones, como con las Mascotas, nos interesará declarar los objetos con la clase padre (o superclase) y a la hora de construirlo hacerlo con la clase hija (o subclase) correspondiente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>

using namespace std;

class Edificio
{
public:
  Edificio()
  {
    cout << "Se construye el edificio" << endl;
  }

  virtual ~Edificio()
  {
    cout << "Sacamos las bombillas del ascensor" << endl;
    cout << "Se destruye el edificio" << endl;
  }

private:
};

class Planta : public Edificio
{
public:
  Planta()
  {
    cout << "Se construye la planta" << endl;
  }

  ~Planta()
  {
    cout << "Sacamos los muebles de los pasillos" << endl;
    cout << "Se destruye la planta" << endl;
  }

private:
};

class Habitacion : public Planta
{
public:
  Habitacion()
  {
    cout << "Se construye la habitación" << endl;
  }

  ~Habitacion()
  {
    cout << "Saco las cosas de la habitación "<<endl;
    cout << "Se destruye la habitación" << endl;
  }

private:
};


int main()
{

  Edificio *h = new Habitacion();

  delete h;
  return 0;
}

La respuesta esperada es la siguiente:

Se construye el edificio
Se construye la planta
Se construye la habitación
Saco las cosas de la habitación
Se destruye la habitación
Sacamos los muebles de los pasillos
Se destruye la planta
Sacamos las bombillas del ascensor
Se destruye el edificio

Pero si quitamos el virtual, la respuesta será la siguiente:

Se construye el edificio
Se construye la planta
Se construye la habitación
Sacamos las bombillas del ascensor
Se destruye el edificio

Muchas veces, los destructores no tendrán nada, cuando no hay nada que hacer, pero imaginad que el Edificio reserva memoria para almacenar ciertos datos, la Planta, reserva también memoria para algo, y la Habitación por su parte también, en ese caso sería necesario pasar por todos los destructores para que cada uno libere su región de información. Así evitamos memory leaks.

En el caso de poner virtual en todos los destructores, no pasa nada.

Más ejemplos

Otro ejemplo puede ser cuando queremos crear un árbol, y los nodos pueden ser de dos tipos (nodo con ramificaciones, o sin ellas):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Nodo
{
public:
  Nodo(string n)
  {
    nombre=n;
    cout << "Construyo el nodo "<<nombre<<endl;
  }

  virtual ~Nodo()
  {
    cout<< "Destruyo el nodo "<<nombre<<endl;
  }
 
  virtual void print(int espacios)
  {
    cout << "Nodo: "<<nombre<<endl;
  }

public:
  string nombre;
};

class Rama: public Nodo
{
public:
  Rama(string n):Nodo(n)
  {  
    cout << "Construyo la rama "<<n<<endl;
  }

  ~Rama()
  {
    while (!nodos.empty())
      {
    delete nodos.back();
    nodos.pop_back();
      }
    cout <<"Destruyo la rama " << nombre << endl;
  }

  void addNodo(Nodo *n)
  {
    nodos.push_back(n);
  }

  void print()
  {
    cout << "Rama: "<<nombre<<endl;
    for (vector<Nodo*>::iterator it=nodos.begin(); it!=nodos.end(); ++it)
      {
    (*it)->print();
      }
  }

public:
  vector<Nodo*> nodos;
};

int main()
{
  Nodo *arbol= new Rama("main");

  Nodo *nodo= new Nodo("Abuelo sin nietos");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  nodo= new Rama("Abuelo 2");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  Nodo *nodo2= new Nodo("Hijo 1");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo2= new Rama("Padre 1");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  Nodo *nodo3= new Nodo("Hijo 3");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 4");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 5");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo3= new Nodo("Hijo 6");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);
  nodo2= new Nodo("Hijo 2");
  static_cast<Rama*>(nodo)->addNodo(nodo2);

  nodo= new Rama("Abuelo 3");
  static_cast<Rama*>(arbol)->addNodo(nodo);
  nodo2= new Nodo("Hijo 7");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo2= new Rama("Padre 2");
  static_cast<Rama*>(nodo)->addNodo(nodo2);
  nodo3= new Nodo("Hijo 8");
  static_cast<Rama*>(nodo2)->addNodo(nodo3);

  cout << endl;
  arbol->print();
  cout << endl << endl;

  delete arbol;
}

En este caso, si quitamos el virtual al destructor, podemos ver que sólo se destruye el nodo main, quedándose todo lo demás en memoria.

Otros ejemplos de este uso pueden ser:

Foto:  <a href=»http://www.flickr.com/photos/randomurl/445352972/»>WagsomeDog</a> (Flickr) Compartido con CC-by a 28/Nov/2011

También podría interesarte....