Moje odkazy
Obsah článku:
vydáno: 9. 9. 2014 10:06, aktualizováno: 5. 11. 2023 13:24
C++ nemá garbage collector (GC) a navíc si s sebou táhne historické dědictví jazyka C a ruční správy paměti. Proto je spousta programů v C/C++ docela peklo a udržovat kód prolezlý pointery je procházka minovým polem. Ale nemusí tomu tak být – v moderním C++ se používá princip RAII a další vymoženosti.
V programu pracujeme s hodnotami, datovými strukturami a dalšími zdroji – a potřebujeme je zavírat a uvolňovat ve chvíli, kdy už je nepotřebujeme (jinak nám dojde paměť, přetížíme síť nebo třeba přijdeme o data).
Následující (pseudo)kód funguje dobře…
// např. otevřeme soubory, síťová nebo databázová spojení atd.
Zdroj a = vytvořímeZdroj();
Zdroj b = vytvořímeZdroj();
// vlastní užitečná práce
něcoDělámeSeZdroji(a, b);
// odpojíme se, zavřeme soubory atd.
zavřemeZdroj(b);
zavřemeZdroj(a);
…ale jen v případě, že metoda něcoDělámeSeZdroji()
nevyhodí výjimku. A nejen to: nesmíme na napsání posledních dvou řádků zapomenout a když tam budeme mít nějaké větvení IF/ELSE, musí být ve všech větvích nebo někde na konci.
Zkratka RAII znamená Resource Acquisition Is Initialization. Tenhle název je trochu kryptický – že se zdroj alokuje v době vytváření objektu není až tak překvapivé. Podstatnější je ta druhá polovina – rušení objektu a uvolňování zdroje. Také se používá zkratka CADRe: Constructor Acquires, Destructor Releases. Nebo výstižnější SBRM: Scope-Based Resource Management (viz diskuse pod článkem).
V RAII je zdroj spjatý s životním cyklem objektu. Na konci rozsahu platnosti proměnné se volá destruktor objektu a ten uvolní daný zdroj (např. zavře soubor).
Když někde vyletí výjimka, rovněž končí rozsah platnosti proměnných a uvolňují se zdroje (automaticky, aniž bychom je museli ručně zavírat).
Zdroje se uvolňují v opačném pořadí, než v jakém vznikaly. Takže když pracujeme např. s SQL databází, nejdřív se uvolní výsledková sada, pak připravený dotaz a až nakonec databázové spojení – tzn. nepodřízneme si pod sebou větev tím, že bychom nejdřív zavřeli spojení a pak až ty další zdroje (což už by bylo k ničemu).
V Javě destruktory nejsou a máme metodu finalize()
, kterou třída může volitelně implementovat (v předkovi java.lang.Object
je prázdná). Tyto metody volá GC při rušení objektů, ovšem není zaručeno, že je zavolá ve správném pořadí a dokonce ani to, že je vůbec zavolá.
Cesta k RAII v Javě vede jinudy. Od verze 7 máme k dispozici vylepšený blok try/catch ve kterém můžeme alokovat zdroje – třídy implementující rozhraní AutoCloseable
. Na konci bloku (tzn. na konci rozsahu platnosti dané proměnné) se automaticky zavolá metoda close()
.
Můžeme zanořit více bloků a zdroje se nám zavřou ve správném pořadí:
try (Zdroj a = new Zdroj("a", false, false, false)) {
try (Zdroj b = new Zdroj("b", false, false, false)) {
try (Zdroj c = new Zdroj("c", false, false, false)) {
a.necoDelej();
b.necoDelej();
c.necoDelej();
} // končí rozsah platnosti proměnné c → volá se c.close()
} // b.close()
} // a.close()
(celý příklad najdete v mém mercurialu)
Pořadí událostí vidíme na výpisu:
Vytváříme: Zdroj [a] chyba=false Vytváříme: Zdroj [b] chyba=false Vytváříme: Zdroj [c] chyba=false Něco děláme: Zdroj [a] chyba=false Něco děláme: Zdroj [b] chyba=false Něco děláme: Zdroj [c] chyba=false Zavíráme: Zdroj [c] chyba=false Zavíráme: Zdroj [b] chyba=false Zavíráme: Zdroj [a] chyba=false
V Javě tedy můžeme dosáhnout srovnatelného efektu jako s RAII v C++. Rozdíly jsou dva:
try (…) { … }
, což vede k více úrovním zanoření kódu Je tu ještě jedna zajímavost. Projděte si celý příklad (viz odkaz výše) a všimněte si, že se v catch
bloku odchytávají nejen výjimky vzniklé při práci se zdrojem, ale i případné výjimky vyhazované z konstruktoru zdroje (nebo obecně při jeho vytváření) a stejně tak výjimky vzniklé při jeho zavírání.
V praxi může dojít k více výjimkám najednou – např. při vytváření zdroje c
a zavírání zdroje b
– ale blok catch
máme jen jeden a procházet se jím bude jen jednou. Vyhodí/odchytí se pouze první výjimka a další zůstanou potlačené.
Potlačené výjimky můžeme získat voláním metody getSuppressed()
právě na té první výjimce.
Příklad: zdroje si vytvoříme tak, aby vyhazovaly výjimky při zavírání a zdroj c
navíc tak, aby vyhodil výjimku při svém použití:
try (Zdroj a = new Zdroj("a", false, false, true)) {
try (Zdroj b = new Zdroj("b", false, false, true)) {
try (Zdroj c = new Zdroj("c", false, true, true)) {
…
sled událostí potom vypadá takhle:
Vytváříme: Zdroj [a] chyba=false Vytváříme: Zdroj [b] chyba=false Vytváříme: Zdroj [c] chyba=false Něco děláme: Zdroj [a] chyba=false Něco děláme: Zdroj [b] chyba=false Něco děláme: Zdroj [c] chyba=true Zavíráme: Zdroj [c] chyba=true Zavíráme: Zdroj [b] chyba=true Zavíráme: Zdroj [a] chyba=true ChybaDělání: Zdroj [c] ChybaZavírání: Zdroj [c] (potlačená výjimka) ChybaZavírání: Zdroj [b] (potlačená výjimka) ChybaZavírání: Zdroj [a] (potlačená výjimka) Finalizujeme: Zdroj [c] Finalizujeme: Zdroj [b] Finalizujeme: Zdroj [a]
Aby došlo k finalizaci objektů, museli jsme zavolat System.gc()
a System.runFinalization()
– jinak by se dříve ukončil program a metody finalize()
by se nezavolaly.
RAII v C++ není vše-spásné řešení. Takhle idylicky to totiž funguje jen pro lokální proměnné. Ve spojení s chytrými ukazateli (místo klasických surových pointerů), které počítají reference, to ale může z velké míry GC nahradit a z C++ se pak stává relativně příjemné a bezpečné prostředí.
Každý jazyk má něco do sebe a taky nějaké slabiny – počítání referencí ukazatelem není dokonalé a na druhé straně GC se zase může spustit ve chvíli, kdy se vám to zrovna nehodí.
Témata: [softwarové inženýrství] [Java] [C++]
Děkuji za článek i za vysvětlení dalšího dílu v Javě.
Měl bych poznámku:
Pro člověka netušící nic o RAII je článek asi nesrozumitelný. Ve své podstatě termín „constructor acquires, destructor releases“ je nejnepřesnější termín a nejzamlžujícnější, který pro RAII mohl být vymyšlen.
Ve skutečnosti to není destruktor, kdo uvolňuje objekty, stejně jako to není ani ve třídě implementující AutoCloseable. Tou podstatou (v C++ i Javě) je, že kompilátor provede uvolnění lokálních proměnných – tedy uklidí poctivě proměnné na konci oboru platnost. Pokud chcete být srozumitelný, pak srozumitelný a plně výstižný název pro RAII je „scope based resource management“.
Do definice „constructor acquires, destructor releases“ nacpete celé chování Javy. V celé Javě garbage collector uklízí a uvolňuje zdroje pomocí finalize(), a nepotřebujete k tomu žádné Autocloseable, tak se chová Java standardně od počátku, že uklízí prostředky destruktorem (budeme-li finalize() považovat za destruktor).
Podstatou RAII není desktruktorové uvolňování zdrojů, ale místo, kde se destruktor volá, totiž na konci oboru platnosti proměnné. Proto autor C++, Bjarne Stroustrup, použil název RAII (resource acquisition is initialization), a nebo jiní SBRM (scope base resource management), ale nikoli nesmyslný název „constructor acquires, destructor releases“. Protože zdroje v RAII nemusí a nejsou vždy uvolňovány destructorem (klidně i ručně dříve), ale hlavně jsou uvolňovány a hlídány oborem platnosti proměnných.
Podstatou RAII je, že zdroj nepřežije proměnnou (její obor platnosti), a kompilátor zajistí (ať už v C++, Adě, nebo Javě pomocí AutoCloseable), že se NEJPOZDĚJI uvolní na konci oboru platnosti. Může se uvolnit i dříve, i jinak, než destruktorem.
Důvodem vzniku a vynálezu RAII je kromě automatiky správy zdrojů také nekomplikovanost při ošetřování výjimek (kterou Java trochu zkomplikovala, jak vidím).
Miloslav Ponkrác
Ve své podstatě termín „constructor acquires, destructor releases“ je nejnepřesnější termín a nejzamlžujícnější, který pro RAII mohl být vymyšlen.
„SBRM (scope base resource management)“ je asi nejlepší označení. RAII mi přijde fakt nicneříkající – pointa je přece uvolňování zdroje, ne jeho vytváření, tak proč o něm ve zkratce není ani slovo?
S tím CADRe je to sporné, ono asi nejde být úplně přesný ve zkratce o pár slovech, vždycky tam bude nějaká nepřesnost, ale výstižné mi přijde, že se zdroje (třeba soubory nebo síťová spojení) uvolňují v destruktoru a ne v nějaké metodě close()
, kterou by člověk musel ručně volat (a tím pádem na ni mohl zapomenout nebo by se mohla přeskočit při vyhození výjimky).
Důvodem vzniku a vynálezu RAII je kromě automatiky správy zdrojů také nekomplikovanost při ošetřování výjimek (kterou Java trochu zkomplikovala, jak vidím).
Zrovna výjimky (kontrolované) mi přijdou v Javě o dost užitečnější než v výjimky v C++.
Pokud to byla narážka na nutnost obalování proměnných blokem a více úrovní zanoření, tak s tím souhlasím (bohužel to asi jinak udělat nešlo).
Jenže pointa není v destruktoru, pointa je v době, kdy se zdroj uvolní.
V dnešní době, kdy se mnoho lidí tváří, že neexistují v sw světě nic jiného, než objekty – se zdá příznačné, že to platí jen pro objekty a jejich destruktory. Jenže ony v jazyce existují i celá čísla, reálná čísla, výčtové typy, pole, struktury a další – a to vše se uvolňuje též, i když to není objektem. Pole není obvykle objektem v řadě jazyků, a přesto ho RAII uklidí též, aniž by existoval nějaký „destruktor pro úklid pole“.
Filozoficky to lze vzít také takto, každý alokovaný zdroj se někdy uvolní. Podstatné není, kdo ho uvolní, ale kdy bude uvolněn. Jestli hned, jak skončí platnost proměnné, nebo až garbage collector někdy za několik hodin či dní, nebo až operační systém při ukončování procesu programu, nebo nějaký manažerský objekt, nebo skupinově v nějakém hromadném alokátoru, nebo jakkoli jinak.
Lidé jsou většinou až nezdravě soustředění na OOP, jako kdyby neexistovalo nic jiného. Z OOP se dělá zlaté tele a modlí se k němu, a pak řada lidí má klapky na očích a není schopno vidět nic jiného, než třídy a objekty.
RAII, neboli SBRM předpokládá, že máme dobrý programovací jazyk, který poctivě inicializuje proměnné v dobách vzniku a poctivě tyto proměnné finalizuje, tedy uklidí na konci jejich platnosti. Mělo by to být samozřejmostí v každém programovacím jazyce, ale bohužel není. V C++ to samozřejmostí je. Tudíž není třeba zdůrazňovat roli „uklízeče“, tím je primárně kompilátor. Stačí říci, že „získání zdroje bude inicializací proměnné“ a pak není třeba říkat více, kompilátor dobře navrženého programovacího jazyka automaticky ke každé inicializaci provádí finalizaci proměnné, tedy úklid, a proto RAII.
Principem RAII není destruktor, ale kompilátor, který poctivě finalizuje vše, co bylo inicializováno. Bez kompilátoru s touto vlastností si můžete destruktorovat do alelujá, ale úklid zdroje proveden stejně nebude.
Miloslav Ponkrác
Zrovna výjimky (kontrolované) mi přijdou v Javě o dost užitečnější než v výjimky v C++. Pokud to byla narážka na nutnost obalování proměnných blokem a více úrovní zanoření, tak s tím souhlasím (bohužel to asi jinak udělat nešlo).
Nereagoval jsem na nic. Jen jsem psal, že RAII byl vymyšlen pro řešení
psaní programů, které bezpečně uvolňují zdroje i přes výjimky.
Ohledně výjimek v C++ a Javě, myslím, že je to prašť jak uhoď. Obecně
považuji implemetaci výjimek v obou jazycích za špatné, možná s tím, že
v C++ to člověk má více pod kontrolou a může si hrany výjimek obrousit k
mírně lepšímu.
Výjimky nejsou nic jiného, než přeskočení zásobníkových rámců zanořených
volání podprogramů a bloků v případě události zvané výjimka. Byla chyba
do výjimek cpát OOP, protože pak režie výjimek jsou obludné a chybové
(např. problém ošetřit výjimku OutOfMemoryError). Jak lze pomocí výjimek
rozumně ošetřovat chybové stavy, kdy je nedostatek zdrojů, když OOP samo
potřebuje pro svou práci zdroje? Nejde. Jak ošetřit chybu, že dochází
místo na zásobníku, když objektový výjimkový systém vám na zásobníku
vytvoří další objekt typu vyhozená výjimka?
Podle mého jediná rozumná implementace výjimek je neobjektová. Tak, jak
to dělá třeba programovací jazyk Ada. Režie takového systému výjimek je
taková, že vyhození výjimky je náročné stejně jako příkaz goto, takže
nikdo režii výjimek v Adě neřeší. Navíc nemůže při vyhození výjimky
nastat chyba, pokud nejsou zdroje – paměť, zásobník, a další.
Java není o efektivitě, a proto Java cpe do objekty typu
java.lang.Throwable nejen chybovou zprávu, ale také třeba celý call
stack. (Mimochodem, v Adě je interně výjimka hodnota typu integer, tedy
žádný objekt, ale přesto řadu těchto informací má také. Ada se zamyslela
a došlo jí, že výjimky přináleží zásobníku, tedy threadu, a tak tyto
informace uložila do threadu, níkoli do nějakého objektu.)
Podlé mého celý výjimkový systém je zkažen tím, že výjimky jsou objekty.
Jak v C++, tak v Javě. Sice v C++ lze vyhazovat výjimkou libovolný typ,
i neobjektový, ale catch bloky stejně obsahují mechanismus pro kontrolu
typu pomocí RTTI, takže si člověk moc nepomůže.
Jenže objektové výjimky umožňují přenášet (užitečnou) informaci, co se vlastně stalo a posílat ji směrem k uživateli – např. jaký soubor se nepodařilo načíst, jaká práva uživatel nemá, který vstup neprošel validací atd. Určitě je lepší dostat chybu:
na řádku 123 u položky xyz má být číslo 0-255 a ne text abcd
místo
chyba čtení konfigurace
Ten nízkoúrovňový pohled a paměťová efektivita jsou sice fajn, ale druhá věc je plnění požadavků a efektivita vývoje – výjimka je alternativa k návratové hodnotě a v tu chvíli chci vědět, co a proč se stalo – a ne mít třeba jen kód chyby 78438. Pomocí kódů jde odlišit jednotlivé typy chyb, ale už z něj nezjistíme, proč chyba vznikla a jak ji můžeme opravit.
1) „Jenže objektové výjimky umožňují přenášet (užitečnou) informaci, co se vlastně stalo a posílat ji směrem k uživateli – např. jaký soubor se nepodařilo načíst, jaká práva uživatel nemá, který vstup neprošel validací atd.“
Takže mi chcete říci, že textové řetězce s chybovou zprávou nejsou mimo objekty možné? To snad nemyslíte vážně.
Bude vám stačit příklad vyhození výjimky v neobjektovém stylu v Adě?:
raise Some_Error with "Soubor /etc/passwd neexistuje";
Proboha proč si myslíte, že jenom high level OOP může nést stav, například textováý řetězec se zprávou? Proměnné, hodnoty a stavy můžete ukládat i neobjektově. Jak to asi dělá to Céčko, když pracuje s řetězci a nemá objekty? Jak to asi dělají neobjektové jazyky, když pracují s řetězci?
qqqqqqqqqqqqqqqqqqq
2) Kromě toho jste nastínil zajímavou filozofickou otázku. K čemu vlastně slouží výjimky? K ošetření nezvyklých situací, nebo k přenášení uživatelských hlášek?
qqqqqqqqqqqqqqqqqqq
3) „Ten nízkoúrovňový pohled a paměťová efektivita jsou sice fajn, ale druhá věc je plnění požadavků a efektivita vývoje – výjimka je alternativa k návratové hodnotě a v tu chvíli chci vědět, co a proč se stalo – a ne mít třeba jen kód chyby 78438. Pomocí kódů jde odlišit jednotlivé typy chyb, ale už z něj nezjistíme, proč chyba vznikla a jak ji můžeme opravit.“
Jak už jsem psal, začnete přemýšlet. OOP je opravdu modla a dává klapky na oči.
Všechno, co píšete, i v té Javě, je nakonec „nízkoúrovňový pohled“, protože se to provádí ve strojovém kódu – nic jiného procesor neumí.
To, jak jsou výjimky vnitřně implementované je pro programátora, tedy i pro Vás nezajímavé – pouze to limituje efektivitu a možnosti použití. Vy nepracujete v programovacím jazyce s vnitřní implementací, ale se syntaxí a prostředím programovacího jazyka, který Vám ten „nízkoúrovňový stroják“ prezentuje „vysokoúrovňově“.
Proč tady argumentujete „nízkoúrovňovaou implemetací“, když se s ní – pokud je programovací jazyk dobře navžen – v programování vůbec přímo nesetkáváte? Nerozumím Vaší námitce ani trochu.
Miloslav Ponkrác
Takže mi chcete říci, že textové řetězce s chybovou zprávou nejsou mimo objekty možné? To snad nemyslíte vážně.
Bude vám stačit příklad vyhození výjimky v neobjektovém stylu v Adě?:
raise Some_Error with "Soubor /etc/passwd neexistuje";
Šlo mi o to, že objektová výjimka může nést strukturovanou informaci – tzn. třeba název souboru v jednom poli a příčinu (práva vs. neexistující soubor atd.) v jiném poli. A až v GUI se z toho složí nějaká lokalizovaná a uživatelsky přívětivá zpráva. Nebo si to naopak zpracuji v nějakém svém systému, strukturovaně zaloguji, nebo se zachovám jinak, podle toho, o jaký pod-typ výjimky jde nebo jakých souborů se týká.
Když budu moci vyhazovat jen primitivní typy, tak si musím vymyslet nějaký systém kódů (což bude odpovídat typům-třídám výjimek, ale nic víc se z toho nedozvím) nebo budu vyhazovat text a vymyslím si nějaký formát – třeba by se to dalo zakódovat jako URL, kde místo domény bude typ výjimky a místo GET parametrů budou parametry této výjimky (tzn. mapa klíč=hodnota).
Pak se to dá považovat za strukturovanou informaci a dá se s tím nějak dál pracovat (ne to jen vyplivnout na STDOUT). Ale když už jsem v OO jazyce, tak přirozeně budu používat objektové výjimky.
„Šlo mi o to, že objektová výjimka může nést strukturovanou informaci – tzn. třeba název souboru v jednom poli a příčinu (práva vs. neexistující soubor atd.) v jiném poli.“
qqqqqqqqqqqqq
1) Já už nevím, jak bych to naznačil. Čím jsou objekty tak výjimečné, že jenom ony – podle Tebe – dokáží nést strukturovanou informaci s několika údaji „v jednom balíku“? Chceš tedy říci, že když budu mít neobjektový jazyk (třeba C), že nelze ničeho takového docílit. Prostě připusuješ objektům výjimečné vlastnosti, které nejsou vlastností pouze objektů.
Tedy i neobjektové výjimky mohou nést strukturovanou informaci o řadě položkách. Neidealizuj si ty třídy a objekty více, než jehovosti Jehovu.
Já už prostě nevím, jak Ti naznačit, že schopnost nést informaci (více položek informace = „strukturovanou informaci“) jsou vlastnostmi objektového i neobjektového světa, tedy objektových i neobjektových výjimek. I to hloupé Windowsí SEH, kterými Microsoft posílá výjimky v C po Windows kernelu tuto schopnost má.
Nemá smysl pokračovat v debatě, kdy z neobjektového světa budeme dělat pitomce, přitom „objektovost“ je iluze. Strojový kód procesoru žádné objekty nezná ani žádné třídy. Do procesoru leze vše neobjektově. OOP je pouze syntaktický cukr někde nahoře pro programátora, nic jiného.
qqqqqqqqqqqqq
2) K čemu vlastně slouží výjimky? Ano, můžeš třeba strukturovanou informaci v rámci „neobjektových výjimek“. Ale na druhé straně – výjimky pak také začínají být neužitečné.
Zkusme modelový případ: Mám chybu otevření souboru. Soubor se jmenuje „soubor.db“, chyba se stala při operaci otevření souboru pro čtení. Skutečná příčina chyby: Není dostatek paměti pro alokaci interního bufferu (512 bajtů) v objektu File, tedy nemusí být dostatek paměti ani pro další objekty přidružené jako další informace k výjimce. Jak budeš takovou chybu ošetřovat?
qqqqqqqqqqqqq
„Když budu moci vyhazovat jen primitivní typy“, tak si musím vymyslet nějaký systém kódů (což bude odpovídat typům-třídám výjimek, ale nic víc se z toho nedozvím) nebo budu vyhazovat text a vymyslím si nějaký formát – třeba by se to dalo zakódovat jako URL, kde místo domény bude typ výjimky a místo GET parametrů budou parametry této výjimky (tzn. mapa klíč=hodnota).“
Psal jsem to hned v první reakci.
1) Proč by všechny předávané informace musely být uloženy ve vyhazovaném typu? Proč? Kdo to řekl? Kdo to tvrdí?
Znovu, proč by vyhazování „primitivního typu“ muselo zůstat na kódech a ničem jiném?
Začínám chápat, že chyba je v tom, že – prosím bez urážky a není v tom snaha sebeméně Tě shazovat – jsi nad výjimkami nikdy nepřemýšlel. Prostě výjimky jsou pro Tebe Java, a nerozumíš tomu, jak to v nich probíhá, ani co se uvnitř děje.
Znáš jenom Java exception API, tedy několik klíčových slov Javy a třídu Throwable, ale nevíš, proč je to tak, co se děje uvnitř, nic.
BTW: neobjektové výjimky používám v PL/pgSQL – a pokud jedna procedura může selhat víc než jedním způsobem, tak to chce si udělat nějaké číslování nebo strukturu toho chybového textu (pokud chce člověk ty případy odlišovat).
Takže ano, jde to – a když není jiná možnost, tak to člověk prostě dělá takhle.
Ale i tam jde k výjimce připojit další atributy jako ERRCODE, MESSAGE, DETAIL… takže je to vlastně zase objekt/struktura.
Všechny informace o procesu, threadu i výjimce jsou struktura.
Co vše si můžeš zjistit o běžícím procesu: pid, název binárky, parametry při spuštění binárky, její práva, čas spuštění, doby konzumace času procesoru, proměnné prostředí, prioritu procesu, uživatele, který proces spustil, a tisíce dalších věcí. Přitom operační systému toto zprostředkovávají i neobjektovým jazykům.
Totéž thread, totéž výjimka.
Ve skutečnosti je více informací o výjimce uloženo mimo objekt výjimky i v Javě a jsou uloženy neobjektovým způsobem. Ale jak jsem psal, výjimky jsou uvnitř docela složité, a kompilátory to předávají programátorovi velmi jednoduchým způsobem, takže pokud se neseznámíš jak to chodí uvnitř a za kompilátorem, asi ta debata dále nebude mít moc smysl.
Zkusím to ještě jinak.
Každý thread, který provádí kód programu existuje z hlediska kódu i kompilátoru v jednom ze dvou stavů: normální běh nebo ošetřování výjimky.
Tedy výjimka není stavem nějakého objektu, výjimka je v první řadě stavem threadu a aby bylo vůbec možné výjimky ošetřovat, musí thread o ní vědět a musí mít od začátku běhu do konce v sobě každý thread mít strukturu něco jako thread_info.
Například ve Windows, kde od kernelu po user programy neexistuje nic bez výjimek ukazuje na x86 procesoru segmentový registr FS na tuto strukturu thread_info, a odtud se odvíjí celé ošetřování výjimek.
Každý thread má celou řadu struktur, jako informací o sobě. Mimo jiné touto strukturou je stack (zásobník), který využívají podprogramy pro lokální proměnné, a nebo thread_info pro podporu výjimek.
Když už jsem mluvil o té Adě, Ada ukládá strukturované informace do thread_info, ačkoli vyhazovanou výjimkou je pouze cleé číslo (kód výjimky). Catch bloky tak nezjišťují žádný typ výjimky, ale pouze porovnávají 2 celá čísla při hledání správného handleru (stejně to dělá Windows kernel a SEH).
Každá výjimka může mít uloženu v Adě (i v SEHu) celou řadu strukturovaných informací, jenom nejsou uloženy ve vyhazované výjimce, ale ve struktuře thread_info. Například chybový text zprávy, název zdrojového souboru, kde výjimka vznikal, číslo řádky, kde vznikla a další.
Jak v neobjektových výjimkách Ady, tak v SEHu, tak v jiných neobjektově provedených výjimkách není problém předat při vyhození výjimky libovolný počet dat o výjimce, pokud o to někdo stojí.
Jediný rozdíl mezi neobjektovými a objektově implementovanými výjimkami je v užitečnosti a efektivitě.
Objektově implementované výjimky si neví rady, když docházejí zdroje. Taková Java nebo C# prostě složí program, pokud dochází paměť, nebo nějaký thread vyčerpal místo na zásobníku. Jde to tak daleko, že třeba Java při výjimce OutOfMemory ignoruje catch bloky, a to i když catch má chytat přímo tuto výjimku. Pak jsou ty výjimky napůl nic neřešící.
Neobjektově implementované výjimky tyto problémy nemají. Dokáží ošetřit jakoukoli chybu, po které bude schopen thread ještě běžet, a to i když zdroje nejsou.
A to je můj poslední příspěvek. Napsal jsem vše, co je třeba. Navíc téma článku je jiné.