Forum: PC-Programmierung C++ Destruktor (richtig) verwenden


von Alexander F. (gustin)


Lesenswert?

Guten Abend,
ich bereite mich gerade für eine Prüfung vor und habe
ein kleines Verständnisproblem mit der richtigen Verwendung
von Destruktoren.

Ein Pseudo-Beispiel:
1
class Bruch{
2
    ...
3
public:
4
    Bruch();
5
    setZahler(int z)...;
6
    setNenner(int n)...;
7
};
8
Bruch(){    // Standardkonstruktor
9
    zahler = 0;
10
    nenner = 1;
11
}

Laut meinen Vorlesungsunterlagen (es gibt exakt zwölf Halbsätze zu
Destruktoren, woraus ich nicht wirklich schlau werde) ist der
Destruktor da um ein Objekt zu löschen, allozierten Speicherplatz
wieder freizugeben und wird vom Compiler nach Ende der Objektlebensdauer
aufgerufen bzw. kann explizit aufgerfen werden.

Jetzt erzeuge ich mir mehrere Instanzen des Objekts:
1
Bruch *testBruch = new Bruch[5];

Hier ist ja testBruch ein Pointer auf das erste Objekt und
es existieren 5 Instanzen auf die ich einzeln zugreifen kann.
In meinem Beispiel etwa mit:
1
testBruch[3].setZahler(12);

So weit so schlecht.
Wie erzeuge ich jetzt einen Destruktor, der ein Objekt "zerstört" und
den Speicher wieder freigibt? Ich weiß ja in der Klasse Bruch nicht
welche Instanzen überhaubt existieren.

Mein Ansatz für das Problem stammt jetzt aus der dynamischen
Speicherverwaltung, dort heißt es, dass man Speicher wieder freigibt,
indem man den Zeiger auf das reservierte Array löscht.
In meinem Fall rufe ich, wenn ich die Objekte nicht mehr brauche
1
delete [] testBruch;
mitten im Programm auf. Ob das überhaupt funktioniert weiß ich nicht.
Dass das nicht richtig sein kann, ist mir aber schon klar, sonst
gäbe es die Destruktoren ja nicht, ich habe trotzdem wirklich keine Idee
wie das richtig funktioniert.
Ich habe jetzt auch keinen Destruktor definiert, das Programm tut
trotzdem was es soll. (???)

Würde mich freuen wenn ihr da etwas Licht ins Dunkel bringen könntet.

von Rolf M. (rmagnus)


Lesenswert?

Alexander F. schrieb:
> Laut meinen Vorlesungsunterlagen (es gibt exakt zwölf Halbsätze zu
> Destruktoren, woraus ich nicht wirklich schlau werde) ist der
> Destruktor da um ein Objekt zu löschen, allozierten Speicherplatz
> wieder freizugeben und wird vom Compiler nach Ende der Objektlebensdauer
> aufgerufen

Ja, das ist korrekt.

> bzw. kann explizit aufgerfen werden.

Ich würd's eher so ausdrücken: Man kann das Objekt explizit zerstören, 
ohne dabei den Speicher freizugeben. Dabei wird der Destruktor 
aufgerufen.

> Jetzt erzeuge ich mir mehrere Instanzen des Objekts:
> Bruch *testBruch = new Bruch[5];
>
> Hier ist ja testBruch ein Pointer auf das erste Objekt und
> es existieren 5 Instanzen auf die ich einzeln zugreifen kann.

Richtig.

> Wie erzeuge ich jetzt einen Destruktor, der ein Objekt "zerstört" und
> den Speicher wieder freigibt? Ich weiß ja in der Klasse Bruch nicht
> welche Instanzen überhaubt existieren.

Es geht nicht um den Speicher, der für das Objekt selbst reserviert ist, 
sondern um Speicher bzw. allgemeiner alle Ressourcen, die deine Klasse 
intern verwaltet. Das gehört zum Thema Kapselung. Die Objekte deiner 
Klasse halten intern irgendwelche Ressourcen, an die man von außen in 
der Regel gar nicht direkt ran kommen soll, die aber spätestens zum Ende 
der Lebensdauer des Objekts freigegeben werden müssen.
Nehmen wir mal an, dein Bruch würde Zähler und Nenner dynamisch 
allokieren. Das wäre zwar wenig sinnvoll, aber verdeutlicht hier das 
Problem:
1
class Bruch
2
{
3
    ...
4
public:
5
    Bruch();
6
    setZahler(int z)...;
7
    setNenner(int n)...;
8
private:
9
    int* zaehler;
10
    int* nenner;
11
};
12
13
Bruch()
14
{
15
    zahler = new int(0);
16
    nenner = new int(1);
17
}

Hier werden Zähler und Nenner im Konstruktor dynamisch allokiert, wenn 
dein Bruch erzeugt wird. Aber wo werden sie wieder freigegeben? Dein 
delete [] testBruch gibt die Bruch-Objekte selbst wieder frei und den 
Speicher, der für die Zeiger benötigt wird, aber die von dir im 
Konstruktor dynamisch erzeugten ints sind noch da, und du hast die 
Zeiger darauf verloren, hast also auch keine Chance mehr, sie jemals 
wieder freizugeben. Und genau da kommt der Destruktor ins Spiel:
1
~Bruch()
2
{
3
    delete zaehler;
4
    delete nenner;
5
}

Und das bezieht sich natürlich nicht nur auf dynamischen Speicher, 
sondern auf alle Ressourcen, wie z.B. geöffnete Dateien oder bei einer 
GUI ein Fenster-Handle. Spätestens wenn das Fenster-Objekt zerstört 
wird, muss auch das Handle und alle Ressourcen, die zum Fenster gehören, 
wieder freigegeben werden.

> Mein Ansatz für das Problem stammt jetzt aus der dynamischen
> Speicherverwaltung, dort heißt es, dass man Speicher wieder freigibt,
> indem man den Zeiger auf das reservierte Array löscht.

Ja.

> In meinem Fall rufe ich, wenn ich die Objekte nicht mehr brauche
> delete [] testBruch;
> mitten im Programm auf. Ob das überhaupt funktioniert weiß ich nicht.

Das funktioniert.

> Dass das nicht richtig sein kann, ist mir aber schon klar, sonst
> gäbe es die Destruktoren ja nicht, ich habe trotzdem wirklich keine Idee
> wie das richtig funktioniert.

Es ist in deinem Fall richtig, da du keine Ressourcen hast, die in der 
Klasse verwaltet werden müssen.

von Dumdi D. (dumdidum)


Lesenswert?

Alexander F. schrieb:
> Dass das nicht richtig sein kann, ist mir aber schon klar, sonst

Doch, ist richtig.

von Alexander F. (gustin)


Lesenswert?

Vielen Dank für die Ausführungen!
So macht das Ganze natürlich Sinn, auf die Idee wäre ich selbst
wohl nicht so schnell gekommen, jetzt ist der Groschen gefallen.

von Hulk (Gast)


Lesenswert?

Richte dir Valgrind ein, damit kannst du genau so etwas schön 
analysieren und dir verdeutlichen.

Gerade wenn du das erste Mal Speicher selbst anfasst (wobei das bei C++ 
in der Regel oftmals nicht erforderlich ist) ist sowas eine große Hilfe. 
Da siehst du dann gleich ob du z.B. außerhalb eines Arrays auf etwas 
zuzugreifen versuchst wo du sonst nur ein Segfault bekommst und mit GDB 
nachschauen müsstest.

Oftmals brauchst du aber eh kein new.


https://wiki.eclipse.org/Linux_Tools_Project/Valgrind/User_Guide


PS: Solltest du wirklich Brüche verwenden wollen:
http://www.boost.org/doc/libs/1_66_0/libs/rational/rational.html

von Daniel A. (daniel-a)


Lesenswert?

Destruktoren werden einfach automatisch aufgerufen, sobald man das 
Objekt zerstört. Dies kann geschehen, wenn man den Scope verlässt in 
welchem das Objekt erstellt wurde, oder, bei dynamischer 
Speicherverwaltung, wenn man delete nutzt, oder das Objekt anderweitig 
zerstört wird. Shared pointer rufen intern auch nur delete auf, wenn der 
reference count 0 ist. Der destruktor wird jedoch nicht aufgerufen, wenn 
man free nutzt, oder man das Objekt anderweitig manuell verwaltet, aber 
wie das geht braucht niemand zu wissen, ist ziemlich nutzlos.

Während deshalb Destruktoren häufig zum Aufraumen von Dingen die zum 
Objekt gehören verwendet werden, gibt es in C++ noch etwas speziellere 
Anwendungszwecke, wie z.B. lock-guards.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.