Schwarze Magie hinter den Kulissen des GCC?
-
Hattet ihr schonmal einen Instanzenzähler mit einem Wert von -36? Ich schon. Im Prinzip versuche ich nur, einen zweidimensionalen Vektor mit Instanzen einer einfachen Klasse zu füllen. Dabei melden sich der Konstruktor und Destruktor jeweils mit std::cout zu Wort.
Langer Rede kurzer Sinn, hier mein Code:
#include <vector> #include <iostream> #define WIDTH 5 #define HEIGHT 3 int instancecounter; class hasZahl { public: int x; hasZahl (int a) { instancecounter++; x=a; std::cout << "c: " << a << " "; } ~hasZahl () { instancecounter--; std::cout << "d: " << x << " "; } }; int main () { instancecounter=0; std::vector<std::vector<hasZahl> > V; std::vector<hasZahl>* B; for (int i=0; i<WIDTH; i++) { B = new (std::vector<hasZahl>); for (int j=0; j<HEIGHT; j++) { B->push_back (*(new hasZahl(10*i+j))); } std::cout << "\npush_back col " << i << std::endl; V.push_back(*B); std::cout << "\npush_back col " << i << " done" << std::endl; } std::cout << std::endl; for (int y=0; y<HEIGHT; y++) { for (int x=0; x<WIDTH; x++) { std::cout << V.at(x).at(y).x << " "; } std::cout << std::endl; } std::cout << "\nexisting instances: " << instancecounter << "\n" ; V.clear(); std::cout << "\ndone\n"; std::cout << "\nexisting instances: " << instancecounter << "\n" ; return 0; }
Und hier das (sehr merkwürdige) Ausgabeverhalten:
c: 0 c: 1 d: 0 c: 2 d: 0 d: 1 push_back col 0 push_back col 0 done c: 10 c: 11 d: 10 c: 12 d: 10 d: 11 push_back col 1 d: 0 d: 1 d: 2 push_back col 1 done c: 20 c: 21 d: 20 c: 22 d: 20 d: 21 push_back col 2 d: 0 d: 1 d: 2 d: 10 d: 11 d: 12 push_back col 2 done c: 30 c: 31 d: 30 c: 32 d: 30 d: 31 push_back col 3 push_back col 3 done c: 40 c: 41 d: 40 c: 42 d: 40 d: 41 push_back col 4 d: 0 d: 1 d: 2 d: 10 d: 11 d: 12 d: 20 d: 21 d: 22 d: 30 d: 31 d: 32 push_back col 4 done 0 10 20 30 40 1 11 21 31 41 2 12 22 32 42 existing instances: -21 d: 0 d: 1 d: 2 d: 10 d: 11 d: 12 d: 20 d: 21 d: 22 d: 30 d: 31 d: 32 d: 40 d: 41 d: 42 done existing instances: -36
Warum ist das alles so seltsam? Wie ist das mit anderen Compilern?
-
*hab mich verlesen*
-
Stichwort: Kopierkonstruktor.
-
Desweiteren erstellst du ein Element mit new, dereferenzierst es und speicherst dann die Kopie davon in den Vector. Memory Leak.
-
Kurz: Sämtliche Verwendungen von Pointern bei dir sind FALSCH:
-
C++ generiert fuer dich einen vorgefertigten Kopierkonstruktor, wenn du keinen angibst oder es explizit verbietest.
-
edit: Ach, dummer Fehler meinerseits. Der Kopierkonstruktor ist es, was fehlt.
edit2: Die Verwendung der Pointer ist ungewöhnlich (ehemaliger Java-Programmierer?), aber nicht grundsätzlich falsch. Mit automatischen Variablen kommt das gleiche raus, bloß ohne dass man am Programmende aufräumen muss (was der Threadersteller nicht tut -> Speicherloch).
-
Fellhuhn schrieb:
Stichwort: Kopierkonstruktor.
Aaah jetzt ergibt das alles viel mehr Sinn.
Fellhuhn schrieb:
Desweiteren erstellst du ein Element mit new, dereferenzierst es und speicherst dann die Kopie davon in den Vector. Memory Leak.
Mir war nicht klar, dass die Zuweisung in einen Vektor das Element kopiert.
Fellhuhn schrieb:
Kurz: Sämtliche Verwendungen von Pointern bei dir sind FALSCH:
Entschuldige bitte, ich komme eigentlich aus der C-Welt und habe mit C++ nicht viel Erfahrung. Wie sollte ich es besser machen?
Ich hab mal einen neuen Versuch gestartet:
#include <vector> #include <iostream> #define WIDTH 5 #define HEIGHT 3 int instancecounter; class hasZahl { public: int x; hasZahl (int a) { instancecounter++; x=a; std::cout << "c: " << a << " "; } ~hasZahl () { instancecounter--; std::cout << "d: " << x << " "; } hasZahl (const hasZahl& alt) { instancecounter++; x = alt.x; std::cout << "cp: " << x << " "; } }; int main () { instancecounter=0; std::vector<std::vector<hasZahl> > V (WIDTH, std::vector<hasZahl> (HEIGHT, 1337)); for (int i=0; i<WIDTH; i++) { for (int j=0; j<HEIGHT; j++) { V[i][j].x=10*i+j; } } std::cout << "\n"; for (int y=0; y<HEIGHT; y++) { for (int x=0; x<WIDTH; x++) { std::cout << V.at(x).at(y).x << " "; } std::cout << std::endl; } std::cout << "\nexisting instances: " << instancecounter << "\n" ; V.clear(); std::cout << "\ndone\n"; std::cout << "\nexisting instances: " << instancecounter << "\n" ; return 0; }
Jetzt ergibt der Output für mich Sinn. Vielen Dank
der output ist btw:
c: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337 d: 1337 d: 1337 0 10 20 30 40 1 11 21 31 41 2 12 22 32 42 existing instances: 15 d: 0 d: 1 d: 2 d: 10 d: 11 d: 12 d: 20 d: 21 d: 22 d: 30 d: 31 d: 32 d: 40 d: 41 d: 42 done existing instances: 0
EDIT: Moment, die Destroyer-Aufrufe in der ersten Zeile ergeben immernoch keinen Sinn für mich
-
Das was du jetzt hast ist ok. Wenn du etwas haben willst, das eher deinem ersten Versuch entspricht, kannst du das so machen:
#include <vector> #include <iostream> const int WIDTH=5; // defines sind gefährlich const int HEIGHT=3; // Nutze const, dazu ist es da class hasZahl { static int instancecounter; // Wozu global, wenn es doch zu der Klasse gehört? public: int x; hasZahl (const hasZahl& a) { instancecounter++; x=a.x; std::cout << "cc: " << a.x << " "; } hasZahl (int a) { instancecounter++; x=a; std::cout << "C: " << a << " "; } ~hasZahl () { instancecounter--; std::cout << "D: " << x << " "; } static int get_count(){return instancecounter;} }; int hasZahl::instancecounter=0; int main () { std::vector<std::vector<hasZahl> > V; for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> B; // Lass den Pointerquatsch for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); // Lass den Pointerquatsch } std::cout << "\npush_back col " << i << std::endl; V.push_back(B); std::cout << "\npush_back col " << i << " done" << std::endl; } std::cout << std::endl; for (int y=0; y<HEIGHT; y++) { for (int x=0; x<WIDTH; x++) { std::cout << V.at(x).at(y).x << " "; } std::cout << std::endl; } std::cout << "\nexisting instances: " << hasZahl::get_count() << "\n" ; V.clear(); std::cout << "\ndone\n"; std::cout << "\nexisting instances: " << hasZahl::get_count() << "\n" ; return 0; }
Ausgabe:
C: 0 cc: 0 D: 0 C: 1 cc: 1 cc: 0 D: 0 D: 1 C: 2 cc: 2 cc: 0 cc: 1 D: 0 D: 1 D: 2 push_back col 0 cc: 0 cc: 1 cc: 2 push_back col 0 done D: 0 D: 1 D: 2 C: 10 cc: 10 D: 10 C: 11 cc: 11 cc: 10 D: 10 D: 11 C: 12 cc: 12 cc: 10 cc: 11 D: 10 D: 11 D: 12 push_back col 1 cc: 10 cc: 11 cc: 12 cc: 0 cc: 1 cc: 2 D: 0 D: 1 D: 2 push_back col 1 done D: 10 D: 11 D: 12 C: 20 cc: 20 D: 20 C: 21 cc: 21 cc: 20 D: 20 D: 21 C: 22 cc: 22 cc: 20 cc: 21 D: 20 D: 21 D: 22 push_back col 2 cc: 20 cc: 21 cc: 22 cc: 0 cc: 1 cc: 2 cc: 10 cc: 11 cc: 12 D: 0 D: 1 D: 2 D: 10 D: 11 D: 12 push_back col 2 done D: 20 D: 21 D: 22 C: 30 cc: 30 D: 30 C: 31 cc: 31 cc: 30 D: 30 D: 31 C: 32 cc: 32 cc: 30 cc: 31 D: 30 D: 31 D: 32 push_back col 3 cc: 30 cc: 31 cc: 32 push_back col 3 done D: 30 D: 31 D: 32 C: 40 cc: 40 D: 40 C: 41 cc: 41 cc: 40 D: 40 D: 41 C: 42 cc: 42 cc: 40 cc: 41 D: 40 D: 41 D: 42 push_back col 4 cc: 40 cc: 41 cc: 42 cc: 0 cc: 1 cc: 2 cc: 10 cc: 11 cc: 12 cc: 20 cc: 21 cc: 22 cc: 30 cc: 31 cc: 32 D: 0 D: 1 D: 2 D: 10 D: 11 D: 12 D: 20 D: 21 D: 22 D: 30 D: 31 D: 32 push_back col 4 done D: 40 D: 41 D: 42 0 10 20 30 40 1 11 21 31 41 2 12 22 32 42 existing instances: 15 D: 0 D: 1 D: 2 D: 10 D: 11 D: 12 D: 20 D: 21 D: 22 D: 30 D: 31 D: 32 D: 40 D: 41 D: 42 done existing instances: 0
Wie du siehst, erzeugt deine neue Variante sehr viel weniger unnötiges Kopieren und ist daher vorzuziehen. Allein schon das sofortige Reservieren des Speichers beim Erstellen des Vektors spart bei deiner neuen Variante viel ein. Ich wollte dir nur einmal zeigen, wie man deinen alten Versuch ohne Pointer umsetzt.
edit2: Edit1 war falsch. Korrigiert.
-
Interessanter Einrück-Stil. Habe ich vorher noch nirgends gesehen.
SeppJ schrieb:
std::vector<std::vector<hasZahl> > V; for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> B; for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); } V.push_back(B); }
Hmm... ich hätte das so gemacht:
std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b]; for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b]; for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); } }
-
enormator schrieb:
EDIT: Moment, die Destroyer-Aufrufe in der ersten Zeile ergeben immernoch keinen Sinn für mich
Nochmal sauber erklärt:
Zuerst wird ein temporäres Objekt erzeugt, mit Wert 1337. Dies ist die Vorlage das Element mit dem später der "innere" Vektor gefüllt werden soll.c: 1337
Dieses wird 3x in einen temporären Vektor kopiert. Dieser temporäre Vektor ist die Vorlage für den "inneren" Vektor.
c: 1337 cp: 1337 cp: 1337 cp: 1337
Das temporäre Element 1337 hat seine Schuldigkeit getan, das temporäre Element kann gehen:
c: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337
Der temporäre Vektor wird 5x in den "äußeren" Vektor kopiert. Dies ist der eigentliche Vektor mit dem am Ende gearbeitet wird. Er trägt den Namen
V
. 5 mal 3 Elemente zu kopieren gibt 15 Kopierkonstruktoraufrufe:c: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337
Dann hat der temporäre Vektor der Länge 3 seine Schuldigkeit getan und sein Destruktor wird aufgerufen. Dieser ruft den Destruktor der 3 Elemente auf, die er enthält:
c: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 cp: 1337 d: 1337 d: 1337 d: 1337
Fertig!
-
krümelkacker schrieb:
Hmm... ich hätte das so gemacht:
std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b]; for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b]; for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); } }
Hatte ich. Dafür braucht es aber einen Defaulkonstruktor.
Der Einrückstil: Ups, dass passiert, wenn man direkt aus Emacs C&P benutzt. Emacs benutzt intern nämlich eine Mischung aus Tab und Space für die Einrückung. Das Forum kommt mit den Tabs wohl nicht zurecht oder hat eine andere Tabbreite.
-
krümelkacker schrieb:
Hmm... ich hätte das so gemacht:
std::vector<std::vector<hasZahl> > V[b] (WIDTH)[/b]; for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> [b]&[/b] B[b] = V[i][/b]; for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); } }
Die Version gefällt mir sehr gut. wobei vielleicht noch ein B.reserve (HEIGHT) angebracht wäre, wenn ich das richtig verstanden habe:
std::vector<std::vector<hasZahl> > V (WIDTH); for (int i=0; i<WIDTH; i++) { std::vector<hasZahl> & B = V[i]; B.reserve (HEIGHT); for (int j=0; j<HEIGHT; j++) { B.push_back (hasZahl(10*i+j)); } }
@SeppJ: Vielen Dank für die Erklärung. Mir ist das nun alles viel klarer geworden