Gelöst: Warum wird der Destruktor hier aufgerufen? :Gelöst
-
Hallo liebes Forum, ich habe mal wieder eine Frage.
Ich habe eine Klasse, die ich als std::vector initialisiere. Dort werden dann die Klassenelemente reingepusht und bekommen eine Signatur mit, damit ich prüfen kann, was für ein Klassenelement es ist.
Ich könnte nun jedes Klassenelement nach der Signatur scannen und eine Zählvariable für die Signatur erhöhen. Aber, dachte ich mir, warum soll das nicht direkt der Konstruktor machen.
Dann wird jedesmal, wenn ein Element erschaffen wird, die Zählvariable erhöht.
Klappt auch wunderbar.
Dann dachte ich mir, was beim Konstruktor geht, geht auch beim Destruktor.
Also wenn ich dann ein std::vector Element mit:
.erase() entferne, müßte ja der Destruktor aufgerufen werden, und mein Signaturzähler um 1 verringert.
Bitte den ganz ganz untenstehenden Code betrachten.
Ich habe das Problem dort mal extrahiert und in eine compilefähige Version gebracht.
Was funktioniert ist folgendes:
(Hier mal 2 Varianten von int main()
Variante 1:int main() { A a(1); // Hier lege ich nur Klassenelemnte ohne vector an. A b(2); // Die Konstruktoren machen was sie sollen und es A c(1); // wird erst beim Verlassen von Main mein Destruktor aufgerufen. cout << "ENDE" << endl; return 0; }Nun die zweite Variante:
(Identisch mit dem ganz unten stehenden Code)int main() { vector<A>a; a.push_back(A(1)); // Hier werden auch immer schön die Kontruktoren aufgerufen a.push_back(A(2)); // Aber auch direkt anschließend dann die Destruktoren.. a.push_back(A(3)); a.push_back(A(1)); a.push_back(A(2)); cout << "erase " << endl; a.erase(a.end()-1); // .erase ruft den richtigen Destruktor auf. cout << "ENDE" << endl; return 0; // Beim Verlassen werden dann korrekt die verbleibenden Destruktoren aufgerufen. }Das läuft ja eigentlich wie es soll, jedoch wird nach jedem push_back ein oder mehrere Destruktoren aufgerufen. Ich vermute mal, daß hier der komplette Vector im Speicher verschoben wird, sprich wegkopiert und die "alten" Kopien zerstört. Nur wie verhindere ich dann, das die Destruktoren dabei aufgerufen werden?
Hier der komplette Code mitsamt Klasse in einer to-compile fähigen Version.
#include <iostream> #include <vector> #include <string> using namespace std; class A { public: A(int signa) { testvar=signa; if (testvar==1) v1++; if (testvar==2) v2++; if (testvar==3) v3++; cout << "Construktor : " << "testvar " << testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; } ~A() { if (testvar==1) v1--; if (testvar==2) v2--; if (testvar==3) v3--; cout << "Destruktor : " << "testvar " << testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; } int testvar; static int v1; static int v2; static int v3; }; int A::v1=0; int A::v2=0; int A::v3=0; int main() { vector<A>a; a.push_back(A(1)); a.push_back(A(2)); a.push_back(A(3)); a.push_back(A(1)); a.push_back(A(2)); cout << "erase " << endl; a.erase(a.end()-1); cout << "ENDE" << endl; return 0; }
-
Schau dir mal Folie 79 an. Das hat zwar jetzt nichts mit deiner Klasse zu tun, aber mit dem Prozess den du hier beobachten willst. Wann was erstellt und zerstört wird.
i11www.iti.uni-karlsruhe.de/_media/information/scripts/sprachen/c_einfuehrung.pdf
-
Hallo,
push_back kopiert nun mal die Objekte in den vector und logischerweise werden dann die Destruktoren der Orginalelemente aufgerufen. Warum ist das bei dir ein Problem. Du musst doch lediglich auch einen CopyKonstruktor für deine Klasse bereitstellen welche ebenfalls bei Bedarf die Counter erhöht.
-
Du musst doch lediglich auch einen CopyKonstruktor für deine Klasse bereitstellen welche ebenfalls bei Bedarf die Counter erhöht.
Okay, das hört sich schonmal logisch an.
Ich beschäftige mich ja gerade erst mit dieser ganzen Konstruktor, Destruktor Thematik.
Bisher bin ich immer ohne "selbst Erstellte" ausgekommen aber mittlerweile, je objektorientierter ich werde, desto mehr Mechanismen muß ich nutzen, damit es alles auch gut funktioniert.Dann werde ich mich wohl mal mit den Copy-Construktoren beschäftigen.
Danke nochmal,
Gruß,
DC
-
Nein das ist schon richtig, dass du bei Objektorientierter Programmierung ohne auskommst. Dafür gibt es ja diese implizierten Mechanismen. Nur irgendwo ist halt mal der Anfang, bzw. will man doch ein wenig mehr machen, als nur stupide Objekte zu kopieren.
-
Ja, leider. Je mehr Flexibilität man einbauen möchte, desto komplexer werden die Mechanismen in den Klassen. Dafür sinkt aber zum Glück die Komplexität des restlichen Codes.
-
Wenn du überaus viele "Verwaltungsfunktionen" wie Kopierkonstruktor, Zuweisungsoperator, Destruktor selbst implementierst, machst du etwas falsch. Im Idealfall hast du ein paar wenige Klassen, die Aufgaben wie Speicherverwaltung etc. übernehmen, und in den anderen Klassen reichen dann dank Wertsemantik und RAII die compilergenerierten Funktionen.
Natürlich muss man gewisse Dinge wie den Zähler irgendwo implementieren. Aber sobald er mehrmals benötigt wird, hat er schon wieder eine eigene Klasse verdient. Grundsätzlich ist die Objektorientierung nicht umso höher, je mehr du ein spezielles Kopierverhalten implementieren musst. Eher im Gegenteil.
Andererseits ist es auch nicht nötig, alle Klassen mit Wertsemantik auszustatten. Wenn es keinen Sinn macht, eine Klasse zu kopieren, mach Kopierkonstruktor und Zuweisungsoperator privat und ohne Definition. Dann musst du allerdings andere Container (z.B.
boost::ptr_vector) verwenden.
-
Ja, ich möchte ja in diesem Beispiel, daß die Klasse automatisch weiß, wieviel Elemente bestimmter Signatur in ihr enthalten sind.
Also anstatt jedesmal alle Vector-Elemente nach der Signatur zu scannen um die Zähler von Hand zu setzen, dachte ich mir, soll es die Klasse selbst machen.
Das geht ja auch wunderbar.
Als ich aber dann den Destruktor mit dem gegenteiligen Mechanismus ausstattete,
damit er die Signaturenzähler verringert wenn ein Vector-Element gelöscht wird, bemerkte ich, daß er auch beim Kopieren er Objekte ausgeführt wird.
Das ich nun auch noch einen CopyConstruktor brauche macht die Sache natürlich wieder etwas komplexer.
Denn irgendwie klappt es mit dem auch nicht so richtig.Ich habe nun folgenden Code:
#include <iostream> #include <vector> #include <string> using namespace std; class A { public: A(int a) { testvar=a; if (testvar==1) v1++; if (testvar==2) v2++; if (testvar==3) v3++; cout << "Construktor : " << "testvar " << testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; } A(const A& a) { if (a.testvar==1) v1++; if (a.testvar==2) v2++; if (a.testvar==3) v3++; cout << "Copy Construktor : " << "testvar " << a.testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; } ~A() { if (testvar==1) v1--; if (testvar==2) v2--; if (testvar==3) v3--; cout << "Destruktor : " << "testvar " << testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; } int testvar; static int v1; static int v2; static int v3; }; int A::v1=0; int A::v2=0; int A::v3=0; int main() { vector<A>a; a.push_back(A(1)); a.push_back(A(2)); a.push_back(A(3)); a.push_back(A(1)); a.push_back(A(2)); cout << "---" << endl << A::v1 << endl << A::v2 << endl << A::v3 << endl << endl; cout << "erase " << endl; a.erase(a.end()-1); cout << "---" << endl << A::v1 << endl << A::v2 << endl << A::v3 << endl << endl; cout << "ENDE" << endl; return 0; }Ich habe nun einen CopyConstruktor beigefügt und er wird auch ausgeführt aber er hat da wieder seltsames Verhalten.
Mal hat er die korrekten Werte seines Klassenelement-Attributs dann hat er wiederum völlig falsche Werte. Mir fehlt da absolut der Durchblick, warum er nun trotz CopyConstruktor wieder solche Allüren hat.
(Vielleicht verzichte ich doch lieber auf den Destruktor und erniedrige die Zählvariablen beim Löschen von Hand, aber es muß doch irgendwie gehen)
Hat da jemand eine Idee? Vielleicht den CopyConstruktor anders definieren?Hier die Bildschirm-Ausgabe:
Construktor : testvar 1
1
0
0Copy Construktor : testvar 1
2
0
0Destruktor : testvar 1
1
0
0Construktor : testvar 2
1
1
0Copy Construktor : testvar 2
1
2
0Copy Construktor : testvar 0
1
2
0Destruktor : testvar 0
1
2
0Destruktor : testvar 2
1
1
0Construktor : testvar 3
1
1
1Copy Construktor : testvar 3
1
1
2Copy Construktor : testvar 4082488
1
1
2Copy Construktor : testvar 4063608
1
1
2Destruktor : testvar 4082488
1
1
2Destruktor : testvar 4063608
1
1
2Destruktor : testvar 3
1
1
1Construktor : testvar 1
2
1
1Copy Construktor : testvar 1
3
1
1Destruktor : testvar 1
2
1
1Construktor : testvar 2
2
2
1Copy Construktor : testvar 2
2
3
1Copy Construktor : testvar 4082488
2
3
1Copy Construktor : testvar 4063608
2
3
1Copy Construktor : testvar 1816222830
2
3
1Copy Construktor : testvar 1934958700
2
3
1Destruktor : testvar 4082488
2
3
1Destruktor : testvar 4063608
2
3
1Destruktor : testvar 1816222830
2
3
1Destruktor : testvar 1934958700
2
3
1Destruktor : testvar 2
2
2
1---
2
2
1erase
Destruktor : testvar 1684960544
2
2
1---
2
2
1ENDE
Destruktor : testvar 4082488
2
2
1Destruktor : testvar 4063608
2
2
1Destruktor : testvar 1836411759
2
2
1Destruktor : testvar 1702129253
2
2
1
-
Wie meinem vorherigen Post zu entnehmen ist, gibts auch noch den Zuweisungsoperator, der ebenfalls überladen werden muss.
Übrigens könntest du deine Codeduplizierung etwas verringern...
-
Nexus schrieb:
Wie meinem vorherigen Post zu entnehmen ist, gibts auch noch den Zuweisungsoperator, der ebenfalls überladen werden muss.
Übrigens könntest du deine Codeduplizierung etwas verringern...
Okay, dann werde ich mal in dieser Richtung weiterforschen. Danke..
Gruß,
DC
-
Was deine "Allüren" betrifft, du musst im Copy-Constructor auch testvar kopieren, sonst steht da nur Mist drin.
-
Was deine "Allüren" betrifft, du musst im Copy-Constructor auch testvar kopieren, sonst steht da alles mögliche drin.
-
dein Problem ist, du kopierst dein testvar im Kopier-Konstruktor nicht
versuch es mal so fürs verständnis. evtl hilft das.
class A { public: A(std::string str):testvar(str){std::cout << "CTor: " << testvar << std::endl;} A(const A& a):testvar(a.testvar+"-Copy"){std::cout << "Copy-CTor: " << testvar << std::endl;} // die testvar kopieren! ~A(){std::cout << "DTor: " << testvar << std::endl;} std::string testvar; };Dan einfach statt A(1) mit A("1") aufrufen
-
Paul Müller gab mir den entscheidenden Hinweis.
Oweh.. Da habe ich ja noch erhebliche Defizite was den Umgang mit Konstruktoren angeht.
A (const A& a) { testvar = a.testvar; if (testvar==1) v1++; if (testvar==2) v2++; if (testvar==3) v3++; cout << "Copy Construktor : " << "testvar " << testvar << endl << v1 << endl << v2 << endl << v3 << endl << endl; }Ich war mittlerweile so verwirrt, daß ich mit a.testvar und testvar durcheinander kam.
Klar, wenn der CopyConstruktor eine Kopie erstellt, daß dann auch a.testvar ins neue testvar kopiert werden muß.
Schließlich ist ja a.testvar der Ursprung und nicht das Ziel.
Und es ist ja auch ein CopyConstruktor der eine Kopie erstellt und die Kopie ist ja nicht a sondern a wurde ja übergeben.
Jetzt wo ich drüber nachdenke wird es mir sogar super klar.Vielen vielen Dank für die gute Unterstützung hier.
Trotzdem ergibt sich nun die Frage, wenn der CopyConstruktor eine Kopie des Objektes erstellt, warum muß ich dann a.testvar von Hand nach testvar kopieren?
Und muß ich dann wenn da noch andere Datenstrukturen in der Klasse sind, ALLE von Hand umkopieren?Gruß,
DC
-
So, hab es ausprobiert.
In der Tat, man muß die anderen Datenstrukturen ebenfalls zum Kopieren angeben.
Na, dann ist es mir nun klar, warum es nicht funktioniert hat.