In C++ wird die Ausnahmebehandlung von einem Mechanismus namens Stack-Unwinding begleitet – der automatischen Zerstörung (Destruktion) lokaler Objekte im Aufrufstack, die vor dem Auftreten der Ausnahme erstellt wurden. Dieses Phänomen ist wichtig für die korrekte Freigabe von Ressourcen.
Ursprünglich, bei der Entwurfsplanung von C++, fehlte ein eingebauter Garbage Collector, und die Aufgabe der Freigabe von Ressourcen (Speicher, Dateien, Sockets) lag beim Programmierer. Die Ausnahmebehandlung sollte für eine korrekte Freigabe beim Zurücksetzen des Codes aufgrund eines Fehlers sorgen.
Ohne den Mechanismus der automatischen Zerstörung von Objekten bei einer Ausnahme entstehen Ressourcenlecks. Wenn Ressourcen nicht manuell in jedem Catch-Block freigegeben werden, wird der Code kompliziert und unzuverlässig.
C++ implementiert Stack-Unwinding, wenn bei der Auslösung einer Ausnahme alle Objekte, die im Stack im Sichtbereich vor throw platziert sind, in umgekehrter Reihenfolge ihrer Erstellung zerstört werden. Ihre Destruktoren werden automatisch aufgerufen.
Die klassische Methode, Ressourcen ständig freizugeben, besteht darin, das RAII-Muster (Resource Acquisition Is Initialization) anzuwenden: Alle Ressourcen sind in Objekten „eingewickelt“, die bei ihrer Zerstörung die von ihnen gehaltenen Ressourcen freigeben.
Beispielcode:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("Kann Datei nicht öffnen"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // Rollback, wenn throw auftritt // ... Arbeit mit der Datei throw std::runtime_error("Ein Fehler"); // fclose wird automatisch aufgerufen }
Wichtige Merkmale:
Warum kann man nicht einfach try-catch verwenden und manuell delete oder fclose im Catch-Block aufrufen, um Lecks zu vermeiden?
Antwort:
Das ist unpraktisch und unzuverlässig: Es ist einfach, zu vergessen, die Ressource zu schließen, insbesondere bei mehreren Ausstiegspunkten oder verschachtelten Ressourcen. Destruktoren werden aufgerufen, selbst wenn eine Ausnahme durch eine Funktion „fliegt“, dafür ist kein Catch erforderlich.
Werden Objekte im Heap zerstört, wenn sie nicht „eingewickelt“ in Objekte im Stack bei Stack-Unwinding sind?
Antwort:
Nein. Stack-Unwinding ruft die Destruktoren nur für Objekte auf, die im Stack platziert sind. Damit Heap-Objekte korrekt zerstört werden, müssen sie von einem Objekt im Stack besessen werden (z.B. durch Smart Pointers).
Was passiert, wenn ein Destruktor während des Stack-Unwindings selbst eine Ausnahme auslöst?
Antwort:
Wenn während des Stack-Unwindings während der Zerstörung eines Objekts eine zweite Ausnahme ausgelöst wird, wird das Programm mit std::terminate() beendet. Werfen Sie niemals Ausnahmen aus Destruktoren!
Der Entwickler schließt Dateien und gibt Speicher ohne Anwendung von RAII manuell über Catch-Blöcke frei.
Vorteile:
Nachteile:
Dateien und Ressourcen werden in RAII-Wrapper-Klassen eingewickelt. Ergebnis: Die Freigabe erfolgt in den Destruktoren, unabhängig von throw/catch.
Vorteile:
Nachteile: