Historie:
C++ wurde ursprünglich mit einem Fokus auf Leistung entworfen, daher erfolgt die Ressourcenverwaltung (Speicher, Dateien, Streams, Sockets) oft manuell. Im Falle einer Ausnahme müssen die erfassten Ressourcen bereinigt werden. Stack-Unwinding ist der Mechanismus, den C++ verwendet, um Funktionen beim Auslösen einer Ausnahme korrekt zu beenden.
Problem:
Beim Auslösen einer Ausnahme wird die Kontrolle sofort an die Catch-Blöcke übergeben, während die Zwischenfunktionen "entwirrt" werden: Ihre Destruktoren werden aufgerufen, aber explizite Aufrufe zur Freigabe von Funktionen könnten übersprungen werden (zum Beispiel, wenn keine Objekte verwendet werden, die Ressourcen automatisch freigeben).
Lösung:
In C++ sollte die Freigabe von Ressourcen den Destruktoren anvertraut werden, anstatt die Freigabefunktionen manuell aufzurufen. Das RAII-Muster (Resource Acquisition Is Initialization) ist ein eindeutiger Weg, um die Freigabe von Ressourcen automatisch zu gestalten. Bei Stack-Unwinding wird der Destruktor aufgerufen und die Ressource wird unabhängig vom Ausgangsweg der Funktion freigegeben.
Beispielcode:
#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // Wird geöffnet und korrekt geschlossen, auch bei Ausnahme if (!file.is_open()) { throw std::runtime_error("Datei kann nicht geöffnet werden"); } // ... Datei lesen } // file wird auch bei Ausnahme geschlossen
Wesentliche Merkmale:
Ist delete ptr; im Catch-Block ausreichend für die Speicherbereinigung?
Nein, wenn zwischen der Speicherzuweisung und dem Catch-Block eine Ausnahme auftritt, könnte der Speicher nicht freigegeben werden. Es ist besser, std::unique_ptr zu verwenden oder delete im Destruktor zu schreiben.
Beispielcode:
void foo() { int* data = new int[10]; // ... throw std::runtime_error("fail"); delete[] data; // wird bei Ausnahme nicht aufgerufen }
Kann Stack-Unwinding den Destruktor für ein auf dem Stack platziertes Objekt überspringen?
Nein, alle lokalen Objekte (die nicht vor dem Punkt des Auslösens einer Ausnahme zerstört wurden) werden in umgekehrter Reihenfolge ihrer Erstellung zerstört, die Destruktoren werden garantiert aufgerufen.
Kann man goto oder longjmp verwenden, um aus einem try-Block auszutreten und dabei auf die Aufrufe der Destruktoren zu zählen?
Nein. C++ garantiert das Aufrufen von Destruktoren nur beim Stack-Unwinding aufgrund einer Ausnahme, nicht aufgrund einer fehlerhaften Steuerung (goto, setjmp/longjmp).
Ein Programmierer allokiert Speicher mit new, behandelt Ausnahmen und gibt den Speicher im Catch-Block frei, vergisst jedoch andere Auswege aus der Funktion.
Vorteile:
Nachteile:
Verwendet std::unique_ptr und RAII-Klassen für alle Ressourcen, die Freigabe ist unabhängig von try/catch.
Vorteile:
Nachteile: