In C++ wordt de afhandeling van uitzonderingen vergezeld door de mechanisme van stack unwinding — de automatische vernietiging (destructie) van lokale objecten op de aanroepstack die zijn gemaakt vóór de uitzondering. Dit fenomeen is belangrijk voor een correcte vrijgave van middelen.
Aanvankelijk, bij het ontwerpen van C++, ontbreekt een ingebouwde garbage collector, en de taak van het vrijgeven van middelen (geheugen, bestanden, sockets) ligt bij de programmeur. Uitzonderingsafhandeling moest zorgdragen voor een correcte vrijgave bij het terugrollen van code vanwege een fout.
Zonder het mechanisme van automatische destructie van objecten bij een uitzondering ontstaan middelenlekken. Als middelen niet handmatig worden vrijgegeven in elke catch-blok, wordt de code complex en onbetrouwbaar.
C++ implementeert stack unwinding, waarbij bij het gooien van een uitzondering alle objecten die op de stack in het zicht voor de throw zijn geplaatst, in omgekeerde volgorde van creatie worden vernietigd. Hun destructors worden automatisch aangeroepen.
Een klassieke manier om constant middelen vrij te geven — het toepassen van het RAII-patroon (Resource Acquisition Is Initialization): alle middelen zijn "ingepakt" in objecten, die bij destructie het door hen beheerde middel vrijgeven.
Code voorbeeld:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("Kan bestand niet openen"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // Rollback bij throw // ... werken met het bestand throw std::runtime_error("Enige fout"); // fclose wordt automatisch aangeroepen }
Belangrijkste kenmerken:
Waarom zou je niet gewoon try-catch gebruiken en handmatig delete of fclose aanroepen in de catch-blok om lekken te voorkomen?
Antwoord:
Het is onhandig en onbetrouwbaar: het is gemakkelijk om te vergeten een middel te sluiten, vooral bij meerdere uitstappunten of geneste middelen. Destructors worden aangeroepen, zelfs als de uitzondering "door" de functie vliegt; catch is hiervoor niet nodig.
Zullen objecten in de heap worden vernietigd als ze niet "ingepakt" zijn in objecten op de stack tijdens stack unwinding?
Antwoord:
Nee. Stack unwinding roept destructors aan alleen voor objecten die op de stack zijn geplaatst. Om heap-objecten correct te vernietigen, moeten ze worden beheerd door een object op de stack (bijvoorbeeld via slimme pointers).
Wat gebeurt er als een destructor tijdens stack unwinding zelf een uitzondering gooit?
Antwoord:
Als tijdens stack unwinding een tweede uitzondering wordt gegooid tijdens de vernietiging van een object, zal het programma eindigen met een oproep aan std::terminate(). Gooi nooit uitzonderingen uit destructors!
Een ontwikkelaar sluit handmatig bestanden en geeft geheugen vrij via catch-blokken zonder RAII te gebruiken.
Voordelen:
Nadelen:
Bestanden en middelen worden ingepakt in RAII-wrapping klassen. Resultaat: vrijgave gebeurt in destructors, ongeacht throw/catch.
Voordelen:
Nadelen: