Autor
|
Tema: [Resuelto] Duda con destructores - vector de una clase (Leído 3,723 veces)
|
X3R4CK3R
Desconectado
Mensajes: 74
Divide y vencerás
|
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é? Mirando la documentación oficial de std::vector::push_back, me encuentro con: This 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#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#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#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; }
OutputHa 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
|
|
« Última modificación: 15 Julio 2013, 17:27 pm por X3R4CK3R »
|
En línea
|
|
|
|
0xDani
Desconectado
Mensajes: 1.077
|
Claro, lo ideal es que tengas un vector de punteros a los hijos. En cuanto a esto: 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 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.
|
|
|
En línea
|
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
Desconectado
Mensajes: 1.211
|
This 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: delete c2; delete c3;
No tiene ningún efecto, tienes que usar punteros para apuntar a estos objetos: vector<Child*> children;
|
|
|
En línea
|
|
|
|
X3R4CK3R
Desconectado
Mensajes: 74
Divide y vencerás
|
El problema esque estás creando copiando los objetos cada vez que haces push_back, por eso al hacer esto: delete c2; delete c3;
No tiene ningún efecto, tienes que usar punteros para apuntar a estos objetos: vector<Child*> children;
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*>). 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. 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!
|
|
|
En línea
|
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
¿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. Los mensajes del destructor no vienen del push_back sino de los delete que has puesto.
|
|
|
En línea
|
|
|
|
X3R4CK3R
Desconectado
Mensajes: 74
Divide y vencerás
|
Los mensajes del destructor no vienen del push_back sino de los delete que has puesto.
No, me refiero a los siguientes: 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.
Por otra parte, ya he logrado mi propósito y quiero publicar a continuación el código para compartirlo: class.hpp#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#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#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:Ha 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
|
|
« Última modificación: 15 Julio 2013, 17:01 pm por X3R4CK3R »
|
En línea
|
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
Mm... Pues vaya sorpresa He hecho una miniprueba: #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: #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.
|
|
|
En línea
|
|
|
|
0xDani
Desconectado
Mensajes: 1.077
|
@X3R4CK3R, veo que has entendido a la primera lo que te dije y ha funcionado, me alegra haber sido de ayuda
|
|
|
En línea
|
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
Desconectado
Mensajes: 74
Divide y vencerás
|
@ 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. 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!
|
|
« Última modificación: 15 Julio 2013, 17:30 pm por X3R4CK3R »
|
En línea
|
|
|
|
|
Mensajes similares |
|
Asunto |
Iniciado por |
Respuestas |
Vistas |
Último mensaje |
|
|
[C++] Buscar un dato privado en un vector de clase
Programación C/C++
|
Rockmore
|
7
|
6,247
|
10 Marzo 2011, 18:13 pm
por Rockmore
|
|
|
clase Vector
Java
|
m@o_614
|
1
|
1,943
|
5 Junio 2012, 22:41 pm
por тαптяα
|
|
|
[SOLUCIONADO] Duda con destructores en C++ (que borrar y que no)
Programación C/C++
|
SARGE553413
|
2
|
2,277
|
16 Agosto 2014, 16:57 pm
por SARGE553413
|
|
|
duda con sobrecarga de operadores en clase vector [c++]
Programación C/C++
|
andoporto
|
1
|
3,479
|
5 Diciembre 2014, 08:36 am
por eferion
|
|
|
Vector de una clase
Java
|
user-marcos
|
2
|
1,816
|
18 Julio 2015, 21:28 pm
por user-marcos
|
|