KdokolivZa normálních okolností dochází k destrukci objektů, ale za normálních okolností ví překladač jaké objekty během práce vznikly a tedy je třeba je destruovat.
V případě vyjímky je situace horší. K výjimce dojde někde uprostřed bloku, kde jsou některé objekty zkonstruovány a jiné ještě nejsou. Pak se musí při stack unwind provést destrukce pouze těch objektů, které jsou zkonstruovány. Nojo, ale jak to ten handler pozná?
Pokud se v C++ (myslím teď MSVC) povolí používání exceptions (považují za dobrou funkci možnost to vypínat), pak překladač obohatí každý objekt vytvořený v zásobníku o příznak (int), který říká, zda je objekt již zkonstruován či nikoliv. Kdykoliv program projde konstuktorem toho kterého objektu, jeho příznak změní. Totéž projde-li destruktorem. Exception handler přeložen tak, že postupně projde všechny objekty na zásobníku a ty které mají příznak nastavený destruuje.
To není jediná věc, která je v programu navíc. Na začátku každé funkce (opakuji každé, byť má jeden řádek kódu, ale musí konstruovat nějaký objekt) je vložen blok několika instrukcí, které si poznamenávají adresu exception handleru následující funkce (bloku). Tota informace je uložena do globální proměnné v TLS (thread local storage). Původní hodnota je uložena do stacku. Před ukončením funkce se provede zase zpětná rošáda.
Dalším problémem jsou vyjímky v konstruktorech. Obecně se považuje objekt za nezkonstruováný, pokud v něm vznikne vyjímka. Výjimka v konstruktoru nezavolá destruktor i když by došlo k výjimce na konci konstruktoru (zavolá se jen destruktory member proměnných a base tříd). Největším problémem je výjimka v destruktoru. Je-li hozena během stack unwind, je tato výjimka zachycena v nějakým globálním handleru a ten nemá jinou možnost než program ukončit.
Vznikne-li výjimka během new, handler zavolá delete a paměť se uvolní. Vznikne-li však vyjímka za new, delete se už nezavolá, leda že by výsledek funkce new byl uložen do nějakýho chytrýho pointeru, nebo objektu, který paměť uvolní při destrukci.
Pokud v konstruktoru alokujete paměť pomocí new a pak někdo hodí výjimku (v konstruktoru), musíte se připravit na to, že tuto výjimku je třeba zachytiti a paměť uvolnit, protože destruktor se nezavolá.
Já osobně dávám přednost řízenému oštření chyb.
1) Chyby během konstrukce objektu -> failstate objektu (objekt je platný, ale ve stavu fail, nepracuje)
2) Chyba během funkce -> návratová hodnota oznamující fail, nebo failobject
3) Chyba během funkce (jiná varianta) -> přepnutí objektu do stavu failstate (tohle dělá např. iostream).
4) Poslední dobou používám systém dopředných výjimek. Výjimku mohu hodit, ale nedochází ke stack unwind. Místo toho výjimku odchytí nějaké rozhraní, která může chybu zaznamenat do logu, zobrazit okénko, nebo odchytit jinak a nastavit se do failstate. Chyba se pak vrací z stejně jako v bodě 2) nebo 3) a teprve přesnou specifikaci chyby si přečte funkce z tohoto rozhraní a může na to nějak reagovat. |