[Resuelto] Duda con destructores - vector de una clase

Iniciado por X3R4CK3R, 15 Julio 2013, 15:16 PM

0 Miembros y 2 Visitantes están viendo este tema.

X3R4CK3R

Buenas, repasando el tema de los destructores me he topado con un bache, algo que intuyo que es provocado por std::vector, me explico:

Tengo una clase "Padre" con un vector de clases "Hijos", cada clase Hijo tiene un destructor que indica la "muerte" de ésta. A partir del código que publico a continuación, esperaba que ningún hijo muriese a no ser que le aplique un delete o el programa finalizase, pero no es así.
Cada vez que hago un push_back a un Hijo desde la clase Padre, los Hijos que ya habían anteriormente en el vector son destruidos, ¿por qué?  :huh:

Mirando la documentación oficial de std::vector::push_back, me encuentro con:

CitarThis effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if -and only if- the new vector size surpasses the current vector capacity.

Por lo que entiendo de ahí, el hecho de reubicar memoria, podría causar que se destruya la clase Hijo del vector y se vuelva a construir, pero si así fuese, ¿por qué no se ejecuta el cout que notifica que ha nacido la clase Hijo?


class.hpp
Código (cpp) [Seleccionar]
#include <iostream>
#include <vector>
using namespace std;

class Child
{
   string name;
public:
   Child(string);
   ~Child();
   string getName();
};

class Father
{
   vector<Child> children;
public:
   void addChild(Child*);
   void showNames();
};


class.cpp
Código (cpp) [Seleccionar]
#include "class.hpp"

Child::Child(string n) : name(n)
{
   cout << "Ha nacido " << name << "." << endl;
}

Child::~Child()
{
   cout << "Ha muerto " << name << "." << endl;
}

string Child::getName()
{
   return name;
}

void Father::addChild(Child *c)
{
   cout << c->getName() << " ha sido adoptado." << endl;
   children.push_back(*c);
}

void Father::showNames()
{
   for(unsigned int i=0; i<children.size(); i++)
   {
       cout << "Hijo #" << i << ": " << children.at(i).getName() << endl;
   }
}


main.cpp
Código (cpp) [Seleccionar]
#include "class.hpp"

int main(int argc, char *argv[])
{
   Child *c1, *c2, *c3;
   c1 = new Child("Juan");
   c2 = new Child("Lucas");
   c3 = new Child("Antonio");

   Father f1;

   cout << "---" << endl;
   f1.addChild(c1);
   cout << "---" << endl;
   f1.addChild(c2);
   cout << "---" << endl;
   f1.addChild(c3);
   cout << "---" << endl;

   f1.showNames();

   delete c2;
   delete c3;

   f1.showNames();

   cout << "---" << endl;
   return 0;
}


Output
CitarHa nacido Juan.
Ha nacido Lucas.
Ha nacido Antonio.
---
Juan ha sido adoptado.
---
Lucas ha sido adoptado.
Ha muerto Juan.
---
Antonio ha sido adoptado.
Ha muerto Juan.
Ha muerto Lucas.
---
Hijo #0: Juan
Hijo #1: Lucas
Hijo #2: Antonio
Ha muerto Lucas.
Ha muerto Antonio.
Hijo #0: Juan
Hijo #1: Lucas
Hijo #2: Antonio
---
Ha muerto Juan.
Ha muerto Lucas.
Ha muerto Antonio.

Mientras creaba este post, me he dado cuenta de que estaba haciendo un vector de Hijos (vector<Child>), cuando lo que quería hacer es un vector de punteros a clases Hijos (vector<Child*>).
Ésto soluciona el programa que arriba expongo, pero sigo con la duda de por qué, si no es un vector de punteros, ocurre lo arriba explicado.

Aparte de esta duda, una de las metas de éste código es que al hacerle delete a una clase Hijo, ésta sea borrada del vector<Hijos> de la clase Padre, cosa que creo que no es posible hacer directamente, (o tal vez sí, por eso pregunto), de no ser posible, agradecería cualquier método de "actualización", "verificación" o similar, que detecte que un miembro del vector de punteros ha sido borrado, y también debe ser borrado del vector... No sé si me explico. :X

Saludos

0xDani

Claro, lo ideal es que tengas un vector de punteros a los hijos.

En cuanto a esto:

Cita de: X3R4CK3Runa de las metas de éste código es que al hacerle delete a una clase Hijo, ésta sea borrada del vector<Hijos> de la clase Padre

Lo que puedes hacer es que, al incluir un hijo en la lista, el padre notifique al hijo de que ha sido adoptado, y este guarde la dirección de su padre. Luego, al ser destruido el hijo, que llame a una funcion removeChild() (que habrás de implementar, y que eliminará a un hijo de la lista) con su propia dirección.
I keep searching for something that I never seem to find, but maybe I won't, because I left it all behind!

I code for $$$
Hago trabajos en C/C++
Contactar por PM

amchacon

CitarThis effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if -and only if- the new vector size surpasses the current vector capacity.
Esto no te influye, el recolocamiento es completamente transparente.

El problema esque estás creando copiando los objetos cada vez que haces push_back, por eso al hacer esto:

Código (cpp) [Seleccionar]
delete c2;
delete c3;


No tiene ningún efecto, tienes que usar punteros para apuntar a estos objetos:

Código (cpp) [Seleccionar]
vector<Child*> children;
Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar

X3R4CK3R

Cita de: amchacon en 15 Julio 2013, 16:04 PMEl problema esque estás creando copiando los objetos cada vez que haces push_back, por eso al hacer esto:

Código (cpp) [Seleccionar]
delete c2;
delete c3;


No tiene ningún efecto, tienes que usar punteros para apuntar a estos objetos:

Código (cpp) [Seleccionar]
vector<Child*> children;
Cita de: X3R4CK3R en 15 Julio 2013, 15:16 PMMientras creaba este post, me he dado cuenta de que estaba haciendo un vector de Hijos (vector<Child>), cuando lo que quería hacer es un vector de punteros a clases Hijos (vector<Child*>).
Sí, lo sé, fue solo un descuido, ya corregí el código antes de postearlo, pero no consideré conveniente postear el código actualizado, ya que la duda que me ha surgido ocurre cuando los elementos no son punteros, todos los elementos (no punteros) se destruyen automáticamente al llegar al final del bracket '}' donde se encuentra declarado, pero ¿por qué al hacer un push_back, el resto de elementos que ya estaban en el vector son destruídos? o al menos, la función destructor es ejecutada. No lo entiendo. :/

Me da la impresión de que no me estoy explicando bien, pero no sé como explicarlo mejor, no es algo que necesite resolver, pero es una duda que me desconcierta y quiero aclarar.  :rolleyes:

Cita de: 0xDani en 15 Julio 2013, 15:53 PM
Claro, lo ideal es que tengas un vector de punteros a los hijos.

En cuanto a esto:

Lo que puedes hacer es que, al incluir un hijo en la lista, el padre notifique al hijo de que ha sido adoptado, y este guarde la dirección de su padre. Luego, al ser destruido el hijo, que llame a una funcion removeChild() (que habrás de implementar, y que eliminará a un hijo de la lista) con su propia dirección.

A mí se me había ocurrido algo parecido:
Al llamar a Father.addChild, guardar en el hijo adoptado la dirección del vector de hijos (miembro de la clase Father), y simplemente, cuando un hijo sea borrado, llamar a la función erase del vector, sería un método directo y muy eficaz, el problema está en hacer un puntero al vector, de primera mano no se me ocurre como hacerlo, he probado con templates pero nada... supongo que tendré que dar algún rodeo y hacerlo como bien has dicho.

Gracias a ambos, un saludo! :)

amchacon

Cita de: X3R4CK3R en 15 Julio 2013, 16:25 PM¿por qué al hacer un push_back, el resto de elementos que ya estaban en el vector son destruídos? o al menos, la función destructor es ejecutada. No lo entiendo. :/

Me da la impresión de que no me estoy explicando bien, pero no sé como explicarlo mejor, no es algo que necesite resolver, pero es una duda que me desconcierta y quiero aclarar.  :rolleyes:
Los mensajes del destructor no vienen del push_back sino de los delete que has puesto.
Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar

X3R4CK3R

#5
Cita de: amchacon en 15 Julio 2013, 16:34 PM
Los mensajes del destructor no vienen del push_back sino de los delete que has puesto.

No, me refiero a los siguientes:
Citar
Ha nacido Juan.
Ha nacido Lucas.
Ha nacido Antonio.
---
Juan ha sido adoptado. (push_back(c1))
---
Lucas ha sido adoptado. (push_back(c2))
Ha muerto Juan. (c1 muere por push_back(c2))
---
Antonio ha sido adoptado. (push_back(c3))
Ha muerto Juan. (c1 muere por push_back(c3))
Ha muerto Lucas. (c2 muere por push_back(c3))
---
Hijo #0: Juan
Hijo #1: Lucas
Hijo #2: Antonio
Ha muerto Lucas. (Muerte por delete)
Ha muerto Antonio. (Muerte por delete)
Hijo #0: Juan
Hijo #1: Lucas
Hijo #2: Antonio
---
Ha muerto Juan.
Ha muerto Lucas.
Ha muerto Antonio.

Ahí no hay ningún delete. :huh:




Por otra parte, ya he logrado mi propósito y quiero publicar a continuación el código para compartirlo:

class.hpp
Código (cpp) [Seleccionar]
#include <iostream>
#include <vector>
using namespace std;

class Father;

class Child
{
   string name;
   Father *p;
public:
   Child(string);
   ~Child();
   string getName();
   void assignFather(Father *);
};

class Father
{
   vector<Child*> children;
public:
   void addChild(Child *);
   void removeChild(Child *);
   void showNames();
};


class.cpp
Código (cpp) [Seleccionar]
#include "class.hpp"

Child::Child(string n) : name(n)
{
   cout << "Ha nacido " << name << "." << endl;
}

Child::~Child()
{
   cout << "Ha muerto " << name << "." << endl;
   p->removeChild(this);
}

string Child::getName()
{
   return name;
}

void Child::assignFather(Father *p)
{
   this->p = p;
}

void Father::addChild(Child *c)
{
   c->assignFather(this);
   children.push_back(c);
   cout << c->getName() << " ha sido adoptado." << endl;
}

void Father::removeChild(Child *c)
{
   for(unsigned int i=0; i<children.size(); i++)
   {
       if(c==children.at(i))
       {
           children.erase(children.begin()+i);
           cout << "Hijo #" << i << " (" << c->getName() << ") ha muerto." << endl;
           break;
       }
   }
}

void Father::showNames()
{
   cout << "---" << endl
        << "Numero de hijos: " << children.size() << endl;
   for(unsigned int i=0; i<children.size(); i++)
   {
       cout << "Hijo #" << i << ": " << children.at(i)->getName() << endl;
   }
   cout << "---" << endl;
}


main.cpp
Código (cpp) [Seleccionar]
#include "class.hpp"

int main()
{
   Child *c1, *c2, *c3;
   c1 = new Child("Juan");
   c2 = new Child("Lucas");
   c3 = new Child("Antonio");

   Father f1;

   f1.addChild(c1);
   f1.addChild(c2);
   f1.addChild(c3);

   f1.showNames();

   delete c2;

   f1.showNames();

   return 0;
}


Output:
CitarHa nacido Juan.
Ha nacido Lucas.
Ha nacido Antonio.
Juan ha sido adoptado.
Lucas ha sido adoptado.
Antonio ha sido adoptado.
---
Numero de hijos: 3
Hijo #0: Juan
Hijo #1: Lucas
Hijo #2: Antonio
---
Ha muerto Lucas.
Hijo #1 (Lucas) ha muerto.
---
Numero de hijos: 2
Hijo #0: Juan
Hijo #1: Antonio
---

La utilidad de este código puede ser amplia, en mi caso, estoy desarrolando una Gui para SFML, donde un elemento o Widget(Button, TextBox...) sería la clase hijo y una capa(Layout), la clase padre, que contiene el vector de Widgets.

Un saludo

amchacon

Mm... Pues vaya sorpresa  :-\

He hecho una miniprueba:

Código (cpp) [Seleccionar]
#include <iostream>
#include <vector>

class A
{
  public:
   int cosa;

   A(int i) : cosa(i) { std::cout<<"Constructor invocado: "<<i<<std::endl;}
   A(const A & b){std::cout<<"Constructor copia invocado: "<<b.cosa<<std::endl; this->cosa = b.cosa;}
   ~A() { std::cout<<"Destructor invocado: "<<cosa<<std::endl;}
};
int main()
{
   A Buffer[5] { 0,1,2,3,4};
   std::vector<A> Cosas;
   std::cout<<"Empezando... "<<std::endl<<std::endl;

   for (short i = 0; i < 5;i++)
       Cosas.push_back(Buffer[i]);

   std::cin.get();
   return 0;
}


Te va diciendo los objetos que se van destruyendo, se construyen o se copian... Al parecer a partir de los 3 objetos, el vector intenta recolocar la memoria y para ello tiene que destruir los objetos y volverlos a construir en otra parte.

Pues parece que tenías razón  ;). Por curiosidad hice la prueba con listas:

Código (cpp) [Seleccionar]
#include <iostream>
#include <list>

class A
{
  public:
   int cosa;

   A(int i) : cosa(i) { std::cout<<"Constructor invocado: "<<i<<std::endl;}
   A(const A & b){std::cout<<"Constructor copia invocado: "<<b.cosa<<std::endl; this->cosa = b.cosa;}
   ~A() { std::cout<<"Destructor invocado: "<<cosa<<std::endl;}
};
int main()
{
   A Buffer[5] { 0,1,2,3,4};
   std::list<A> Cosas;
   std::cout<<"Empezando... "<<std::endl<<std::endl;

   for (short i = 0; i < 5;i++)
       Cosas.push_back(Buffer[i]);

   std::cin.get();
   return 0;
}


Aquí no pasa, de modo que está claro que es por el realloc() que se hace.
Por favor, no me manden MP con dudas. Usen el foro, gracias.

¡Visita mi programa estrella!

Rar File Missing: Esteganografía en un Rar

0xDani

@X3R4CK3R, veo que has entendido a la primera lo que te dije y ha funcionado, me alegra haber sido de ayuda  :)
I keep searching for something that I never seem to find, but maybe I won't, because I left it all behind!

I code for $$$
Hago trabajos en C/C++
Contactar por PM

X3R4CK3R

#8
@amchacon: mm, cierto, no recordaba eso del constructor copia, eso era lo que no encajaba en mi teoría, lo que no entiendo es el orden en el que se van copiando y destruyendo los elementos, pero bueno, supongo que dependerá de los bloques de memoria que hayan libres y tal, así que mejor no darle más vueltas. :laugh: Gracias por la aclaración.

@0xDani: Sí, gracias, aunque la verdad es que ya había pensado en ese método, pero antes de desarrollarlo quise dar un paso más y hacerlo de forma más directa, suelo darle muchas vueltas a todo para hacerlo de la forma más eficiente posible. De cualquier forma, el método funciona y puedo trabajar con él.

Gracias a ambos y un saludo!  ;)