Destruktoraufruf

  • Themenstarter Themenstarter C152778
  • Beginndatum Beginndatum
C

C152778

Hallo zusammen,

eine sehr grundlegende Frage beschäftigt mich: Was genau passiert bei einem Destruktoraufruf ?
Dabei geht es mir vorallem um den Standard-Destruktor. Beispiel:
Code:
class A {
public:
  int a;
  A() {
    a = 4;
  }
};
Macht man nun folgenden Aufruf:
Code:
A b;
b.~A();
Was passiert dann mit der Variablen b genau? Gilt sie dann als nicht mehr initialisiert? Wie muss man sich das vorstellen?

Schöne Grüße, Cyraid
 
Du rufst einfach den Destruktor auf der nichts anderes ist als jede andere Funktion auch. Daher ruft man den Destruktor eigentlich auch i.d.R. nie explizit auf. Du kannst auch folgendes simples Beispiel verwenden:
Code:
class Foob
{
	int a;
public:
	Foob() : a(2)
	{ 
	}

	~Foob()
	{
		a = 0;
	}

	void Bar()
	{
		std::cout << a << std::endl;
	}
};

int main()
{
	Foob f;
	f.~Foob();
	f.Bar();
}

Macht nichts anderes als 0 ausgeben.
 
Du rufst einfach den Destruktor auf der nichts anderes ist als jede andere Funktion auch. Daher ruft man den Destruktor eigentlich auch i.d.R. nie explizit auf. Du kannst auch folgendes simples Beispiel verwenden:
[...]
Macht nichts anderes als 0 ausgeben.

Danke für deine Antwort.

Das überrascht mich ein wenig, da ich immer dachte, dass der Destruktor über die Fähigkeit einer normalen Funktion hinausgeht im dem Sinne, dass er das Objekt "zerstört" oder irgendwie invalid/unbenutzbar macht. Das ist also gar nicht der Fall ? Man kann das Objekt in deinem Beispiel wie gewohnt weiternutzen ohne Einschränkungen ?

Schöne Grüße, Cyraid
 
Es ist einfach so:
Ein Anruf auf delete macht ungefähr folgendes:
- Prüfen ob der übergebene Zeiger nicht 0 ist
- Den Destruktor als ganz normale Funktion aufrufen
- Den Speicher freigeben.

Und genau Punkt 2 unterscheidet delete von free. free ruft keinen Destruktor auf.

Eine auf dem Stack platzierte Variable wird am Ende des Scopes auch nicht viel anderes machen. Zuerst wird der Destruktor aufgerufen für alle Objekte, die einen Destruktor besitzen und anschliessend wird einfach der Stackpointer wieder zurückgesetzt.

Der Destruktor an und für sich macht also eigentlich nichts ungültig, er ist nur sozusagen ein Event, das ausgelöst wird, wenn die Instanz "stirbt".
 
@Cromon: Danke für deine Antwort, jetzt ist es mir klarer geworden. :)

Eine weitere Frage (nicht ganz dem ursprünglichen Thema entsprechend, aber das delete brachte mich darauf):
Hat man die Basisklasse A und das Objekt b der abgeleitete Klasse B von A wie folgt deklariert: A* b = new B(...). Ruft man nun delete(b) auf, wird dann nur der Speicher derjenigen Membervariablen freigegeben die zur Basisklasse gehören bzw. wie kann der Compiler wissen, dass es sich hier um ein Objekt von B handelt und damit Speicher weiterer Variablen (die von B) freigeben?

Schöne Grüße, Cyraid
 
Eine weitere Frage (nicht ganz dem ursprünglichen Thema entsprechend, aber das delete brachte mich darauf):
Hat man die Basisklasse A und das Objekt b der abgeleitete Klasse B von A wie folgt deklariert: A* b = new B(...). Ruft man nun delete(b) auf, wird dann nur der Speicher derjenigen Membervariablen freigegeben die zur Basisklasse gehören bzw. wie kann der Compiler wissen, dass es sich hier um ein Objekt von B handelt und damit Speicher weiterer Variablen (die von B) freigeben?
Man muss hier zwei Sachen unterscheiden: einerseits den Speicherbedarf einer Instanz (das, was der sizeof-Operator zurückgibt) und andererseits den zusätzlichen Speicher, den die Instanz während ihrer Lebenszeit ggf. reserviert und verwaltet.

Ersterer Speicher wird von new allokiert und von delete auch wieder vollständig freigegeben (unabhängig vom Zeigertyp). Das funktioniert, weil sich das Betriebssystem für jeden allokierten Speicherbereich merkt, wie groß dieser ist.

Der von der Instanz selbst verwaltete Speicher wird i.d.R. im Destruktor freigegeben – man muss sich also selbst darum kümmern. Dabei ist es aber wichtig, dass bei delete der richtige Konstruktor aufgerufen wird (nämlich der der konkreten Klasse). Darum sollte man in solchen Situationen den Destruktor immer als virtual deklarieren.

Grüße,
Matthias
 
@Matthias: Vielen Dank für die gute Erklärung.

Ich habe mir dazu folgendes Beispiel erstellt:
Code:
class A {
public:
	int a;
};

class B : public A {
public:
	int b;
};

int main() {
	B* b1 = new B;
	cout << sizeof(*b1) << endl; // 8
	A* b2 = new B;
	cout << sizeof(*b2) << endl; // 4
}
Würde bei einem Aufruf von delete(b2) nur der von int a belegte Speicherplatz freigegeben, da der Zeiger (fälschlicherweise) als Objekt der Klasse A interpretiert wird oder werden die vollen 8 Byte Speicher freigegen?
Oder muss man hier zuvor b2 casten in B* , um einen sinnvollen Aufruf von delete(b2) zu erhalten?

Schöne Grüße, Cyraid
 
Es wird der komplette Speicher freigegeben, auf den b2 zeigt, das ist nunmal 8 (bzw. 12, wenn virtuelle Funktionen vorhanden wären) Bytes. Allerdings würde der Destruktor von A aufgerufen, da dieser nicht virtuell deklariert wurde und daher nicht in der VMT nach der wirklichen Adresse für den Destruktor geschaut würde.
 
Zurück