C++ Anfängerfragen zu Konstruktoren/Destruktoren mit Elementen auf dem Heap

Furi

Lt. Junior Grade
Registriert
Okt. 2008
Beiträge
392
Hi @ all,

bin grade am Lernen und hab zu diesem einfachen Stück Code ein paar Fragen:

Code:
#include <iostream>
using namespace std;

int counter=0;	//Globaler Zähler für Konstruktoraufrufe

class complex{
	private:
		float *real;
		float *imag;
	public:
		complex(){						//Konstruktor ohne Parameter
			real = new float;
			*real = 0.0;
			imag = new float;
			*imag = 0.0;
			counter++;
		}
		complex(float r, float i){		//Konstruktor mit Parametern
			real = new float;
			*real = r;
			imag = new float;
			*imag = i;
			counter++;
		}
		complex(complex &copy){			//Kopierkonstruktor mit Call-by-Reference
			real = new float;
			real = copy.real;
			imag = new float;
			imag = copy.imag;
			counter++;
		}
		/*complex(complex *copy){		//Kopierkonstruktor mit Pointer
			counter++;
			real = new float;
			real = copy->real;
			imag = new float;
			imag = copy->imag;
		}*/
		~complex(){
			imag = NULL;
			real = NULL;
		}
		
		void delete_from_heap(){
			delete imag;
			delete real;
		}
		
		void ausgabe(){
			cout << *real << "+i" << *imag << endl;
			cout << "Counter: " << counter << " ";
		}
};

int main(){
	
	complex a;
	a.ausgabe();
	cout << endl << endl;
	
	complex b(4.3,5.7);
	b.ausgabe();
	cout << endl << endl;
	
	complex c(b);
	c.ausgabe();
        cout << endl << endl;

	//complex d = new complex;
	
	c.delete_from_heap();
	//b.delete_from_heap();
	a.delete_from_heap();
	
	return 0;
}

Ich denke mal, der Code dürfte für die meisten hier recht selbsterklärend sein.

Folgende Fragen hätte ich dabei an euch:
  1. Ich hatte mich gewundert, dass der Destruktor, in welchem vorher "delete real; delete imag;" stand, das Programm zum Absturz laufen lässt. Dazu habe ich mir die Funktion "delete_from_heap" angelegt und gesehen, dass die "b"-Instanz, welche den Konstruktor mit einfachen Parametern nutzt, schuld am Absturz ist. Ich kann aber in diesem Konstruktor imag und real gleich mit delete wieder löschen, ohne dass es zum Absturz kommt. (Der Wert der beiden Variablen ist dann natürlich sinnlos, aber es funktioniert). Warum ist das so und wie kann ich es beheben, da mir dadurch ja der Destruktor seinen Sinn verliert und die Elemente/Objekte ja auf dem Heap verbleiben ?!
  2. Woher weiss der Compiler, welchen der beiden Konstruktoren er für die Instanz "c" hernehmen soll, wenn keiner von denen auskommentiert ist ?
  3. Warum wird bei Verwendung des Konstruktors mit Pointer als Parameter die globale Variable (counter) nicht erhöht ?
  4. Eigentlich kann ich es mir schon denken, aber warum funktioniert Zeile 69 nicht ?

Bitte nicht falsch verstehen, ich möchte nicht faul sein und meine Fragen auf das Forum abwälzen. Nur meistens wenn ich nach C(++)-Problemen google, bekomm ich nicht die Antworten die ich will, oder Antworten zur gleichen Frage aber mit komplett anderer Ursache Vielleicht einfach nur zu blöd zum googlen, wer weiss...

Vielen Dank schonmal,
Furi ;)
 
Zuletzt bearbeitet:
Zu Frage 1:

Der Fehler liegt hier (Referenz und Pointer):

Code:
complex(complex &copy){			//Kopierkonstruktor mit Call-by-Reference
			real = new float;
			real = copy.real;
			imag = new float;
			imag = copy.imag;
			counter++;
		}

Lösung:
Code:
complex(complex &copy){			//Kopierkonstruktor mit Call-by-Reference
			real = new float;
*			real = *(copy.real); // Du hast vorher einfach die Adressen übergeben, nicht den Inhalt.
			imag = new float;
		*	imag = *(copy.imag); // Du hast vorher einfach die Adressen übergeben, nicht den Inhalt.
			counter++;
		}

Zu Frage 2: Anhand dessen was du als Parameter übergibst. Entweder Pointer oder Referenz (geratene Lösung).

Zu Frage 3: Setze den Counter als private static int counter in die Klasse. Woher soll die Klasse wissen, dass es außerhalb der Klasse eine Variable counter gibt? Sollte meiner Meinung nach nicht funktionieren.
Als Tipp: Verwende niemals globale Variablen.

Zu Frage 4:
Code:
complex *d = new complex()
 
Zuletzt bearbeitet:
Löblich, dass du am Lernen bist, aber dann lern's bitte gleich richtig: Pointer sind hier völlig fehl am Platz. Verkomplizieren das Ganze unnötig, sorgen für deine Speicherlecks, etc.
 
Dazu habe ich mir die Funktion "delete_from_heap" angelegt und gesehen, dass die "b"-Instanz, welche den Konstruktor mit einfachen Parametern nutzt, schuld am Absturz ist.

Schauen wir mal, was dein Copy-Constructor macht:
Code:
complex(complex &copy){
  real = new float;
  real = copy.real;
  imag = new float;
  imag = copy.imag;
  counter++;
}

Du erstellst richtigerweise neue float-Objekte auf dem Heap über die Sinnhaftigkeit lässt sich streiten, aber ist ja nur ein Beispiel, kopierst dann aber nicht die Werte von copy, sondern die Pointer. Will heißen:
- Die Pointer zu deinen neuen Objekten gehen verloren => Memory Leak
- Du rufst 2x delete mit denselben Pointern auf => Absturz.
Lösung ist wie gesagt, die Werte zu kopieren.

Dann kannst du auch wieder zur ursprünglichen Lösung zurückkehren und die Objekte im Destruktor löschen, denn genau da gehören solche Sachen hin.

Woher weiss der Compiler, welchen der beiden Konstruktoren er für die Instanz "c" hernehmen soll, wenn keiner von denen auskommentiert ist?
b ist als complex deklariert, nicht als complex*. Also kommt nur die Variante mit Referenztypen in Frage.
Nebenbei ist es nicht üblich, einen "Copy-Constructor" mit Pointer zu definieren. Ich weiß auch nicht, wozu das gut sein soll, denn...
Code:
complex* e = new complex();
complex f = *e;
...erreicht genau dasselbe.

Warum wird bei Verwendung des Konstruktors mit Pointer als Parameter die globale Variable (counter) nicht erhöht ?
Der Konstruktor wird in deinem Code nicht verwendet. Wenn du nicht im Code irgendwo explizit mit Pointern gearbeitet hast, wurde statt des "Pointer-Copy-Constructors", den wie gesagt kein Mensch braucht, einfach der Default-Copy-Constructor mit Referenz aufgerufen. Der nebenbei auch für Abstürze sorgen wird.

Eigentlich kann ich es mir schon denken, aber warum funktioniert Zeile 69 nicht ?
Man schaue sich mal den Typen der Variablen an und den Typen, den new complex() zurückliefert.. ;)

Edit:
Woher soll die Klasse wissen, dass es außerhalb der Klasse eine Variable counter gibt?
Wenn sie es nicht wüsste, würde wohl der Compiler meckern. Solange es in der Klasse keine solche Variable gibt, ist das kein Problem, das Dingen ist ja deklariert.

...aber natürlich kein guter Stil.
 
Zuletzt bearbeitet:
Hehe, da sag einer man soll mit C anfangen eine Programmiersprache zu lernen.
Das kommt nur von denen die es bereits können und selbst mit Java angefangen hatten, sich aber die Problematik nicht mehr vorstellen können. Oder so alt sind das C damals die modernste Sprache war :)
 
Habe mir noch nicht alles angesehen, aber Kopierkonstruktoren sollten ihr Argument per const reference empfangen, sonst wird dein Kopierkonstruktor in vielen Fällen nicht mal als solcher erkannt.
 
@rob-
Sich mit (auch schweren) Problemen auseinanderzusetzen gehört zum Lernen dazu...
Außerdem ist das C++ und nicht C.
 
Zuletzt bearbeitet:
rob- schrieb:
Hehe, da sag einer man soll mit C anfangen eine Programmiersprache zu lernen.
Das kommt nur von denen die es bereits können und selbst mit Java angefangen hatten, sich aber die Problematik nicht mehr vorstellen können. Oder so alt sind das C damals die modernste Sprache war :)
Ich habe nie Java programmiert und ebenfalls mit C++ angefangen. Natürlich ist das der steinige Weg, wenn die Runtime einem nicht die halbe Arbeit abnimmt und einen beschützt. Aber mit "Auf die Nase fallen" lernt man mit Problemen umzugehen und sauberen Code zu schreiben.
Und C++ ist noch umfangreicher als C. Man lernt also nicht nur, wie man den Code fehlerfrei schreibt, sondern welche Konzepte und Paradigmen wie und wo am besten eingesetzt werden. Objektorientiert ist nicht alles, Templates auch nicht. Manchmal ist eine bisschen Schlangencode ganz praktisch.

P.S.: Als ich angefangen habe, gab es schon Java, C#, etc.
 
Zuletzt bearbeitet:
Kleiner Tipp zum Programmieren, was du von Anfang an beachten solltest.

Versuche von Anfang an Kommentare im Quellcode zu machen, damit man weiß welcher Text was macht.
Zusätzlich gucke dir nochmal genau an, wie man den Quellcode einrücken sollte, damit dieser besser lesbar ist.
 
Danke erstmal an alle für eure schnelle und kompetente Hilfe !

Da hab ich mich wohl zu sehr auf den falschen Konstruktor versteift, ich hatte tatsächlich gar nicht erst angefangen, beim Kopierkonstruktor zu suchen, schließlich lief der ja...

Den Tipp, globale Variablen zu vermeiden, werde ich beherzigen. Leider sieht unser Prof das aber anders in seinen Übungen, die kommen eigentlich in jeder vor. Vielleicht war das früher anders ?

Frage 4 war tatsächlich dämlich. Anstatt den Code anzusehen, hab ich mich gleich auf ein viel komplexeres Problem versteift. Aber ist das nicht ein bisschen Inception ? Ich lege eine Klasse auf dem Heap an, um dann deren Elemente per Pointer wieder auf dem Heap anzulegen. Sinn machts sicherlich keinen und es liest sich von außen wie ein Heap auf dem Heap. :freak:

Und den Teil mit "const" im Copy-Konstruktor habe ich mir auch notiert !



Ich danke euch allen nochmals.
Ja, um Sinn und Unsinn geht es mir nicht, ich will nur die Möglichkeiten lernen.
 
Zuletzt bearbeitet:
Kleine Anmerkung am Rande:
Es ist natürlich wichtig ist zu verstehen, wie die grundlegenden Sprachprimitiven funktionieren, aber für "echten" Code kannst du dir merken, dass dort Heutzutage praktisch kein direktes new / delete mehr vorkommen sollte (nur gekapselt innerhalb der Standardbibliotheksfunktionen) und ich hoffe, das erklärt euch euer Prof./Tutor auch, bzw. zeigt euch wie das Speichermanagement stattdessen aussehen sollte.

Und zum Thema Heap auf Heap:
Im Normalfall gibt es nur einen Heap, auf dem alle Objekte erstmal gleichberechtigt sind. Eine (evtl. hierarchische) Beziehung zwischen den Objekten ergibt sich erst dadurch, dass Objekte Verweise (Zeiger / Referenzen) auf andere Objekte haben.
 
Diego1989 schrieb:
Kleiner Tipp zum Programmieren, was du von Anfang an beachten solltest.

Versuche von Anfang an Kommentare im Quellcode zu machen, damit man weiß welcher Text was macht.

In seinem Code war jetzt nix so furchtbar kompliziertes, dass sich Kommentare wirklich lohnen würden. Kommentare dienen dazu, nicht unmittelbar offensichtliche Sachverhalte zu erklären. Dinge wie

Code:
// increment a
++a;

sind absolute unnötig. Der Zweck von Kommentaren ist nicht, dem Syntax-Highlighting etwas Grün hinzuzufügen.

P.S. Oder halt rot ... oder wie auch immer dein Editor halt Kommentare einfärbt. :p
 
@Miuwa:

Ich hoffe dann, dass das noch kommt. Unser Modulhandbuch ist - je nach Prof - nur sehr spärlich bedruckt. Bis auf Software-Engineering im 3.ten und Nummerische Methoden/Systemprogrammierung liest sich unser Modulplan fast schon wie ein beschnittener ET-Plan.
Ansonsten gibts ja noch Bücher...
 
Zurück
Oben