ProgrammatieC++ Backend ontwikkelaar

Beschrijf de verschillen tussen stack unwinding en het vernietigen van objecten bij het afhandelen van uitzonderingen in C++. Hoe garandeer je een correcte vrijgave van middelen bij een uitzondering?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord.

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.

Geschiedenis van de vraag

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.

Probleem

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.

Oplossing

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:

  • Stack unwinding roept automatisch de destructors aan van alle objecten op de stack.
  • RAII garandeert de vrijgave van middelen ongeacht of er een uitzondering optreedt.
  • Handmatige vrijgave van middelen in catch is zelden nodig: destructors doen dit beter.

Vragen met valstrikken.

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!

Typefouten en antipatronen

  • Niet de middelen vrijgeven in destructors, vertrouwend op handmatige beheersing in catch.
  • Uitzonderingen gooien uit destructors.
  • Geen slimme pointers gebruiken voor het beheer van heap-middelen.
  • Vergeten van geneste middelen (bijvoorbeeld een bestand binnen een structuur).

Voorbeeld uit het leven

Negatieve case

Een ontwikkelaar sluit handmatig bestanden en geeft geheugen vrij via catch-blokken zonder RAII te gebruiken.

Voordelen:

  • Beheert middelen expliciet.

Nadelen:

  • Bij het verlaten via throw naar catch zijn er geen leaks te vermijden.
  • De code is moeilijk te onderhouden en aan te passen, en het is gemakkelijk vergeten middelen vrij te geven.

Positieve case

Bestanden en middelen worden ingepakt in RAII-wrapping klassen. Resultaat: vrijgave gebeurt in destructors, ongeacht throw/catch.

Voordelen:

  • Betrouwbaarheid van middelen vrijgave.
  • Minder code, gemakkelijk te onderhouden.

Nadelen:

  • Je moet kunnen schrijven van RAII-wrappers.
  • Het toevoegen van nieuwe soorten middelen vereist nieuwe klassen.