11. gyakorlat¶
Ismétlés¶
- pure virtual
- virtual desktruktor
- további öröklődés példák
Hibakezelés¶
A programok futás közben különféle hibákba futhatnak, amik egy részére felkészíthetjük programunkat. Lehetnek rendszerhez kapcsolódó hibák, például elfogy a memória, nincs megfelelő jogosultság egy erőforráshoz vagy akár felhasználói hiba, például amikor szám helyett szöveget adnak meg a konvertáló függvénynek. Ezeket a hibákat kezelni kell, különben definiálatlan viselkedéssel folytatódhat tovább a program futása.
Hibakezelésre láttunk már példát a stringek számmá konvertálásakor és a dinamikus memóriakezeléáskor. Ekkor a hibát egy Exception jelezte, hogy a program végrehajtása során hiba lépett fel. Ekkor nem fut le teljesen a függvény (megszakad a futás a hiba eldobásakor), nem adja vissza a megfelelő értéket, hanem jelzi, hogy adott egy objektum, az tartalmazza a hiba részleteit, onnantól azzal kell foglalkozni.
Az ilyen megjelenő objektumokat try-catch párossal tudjuk kezelni. Minden a try ágban lévő hibát a catch ágban/ágakban tudunk kezelni. Például:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Látható, hogy a try ágban megpróbálunk végrehajtani egy feladatot, de hibát kapunk. A catch ágban azonban több részletet is ki kel emelni:
- const: A catch ág egy objektumot kap el ami lehet konstans, hiszen nem akarjuk megváltoztatni a hiba jellemzését, ezért amikor elkapjuk const típust kell megadni
- referencia: A hiba objektumok gyakran osztályok (erről később) ezért lehet öröklődési viszony köztük. Ahhoz, hogy az ilyen öröklődés polimorfikusan tudjon viselkedni, referenciát kell használnunk.
- what metódus: az előre beépített hibáknak van egy meghatározott metódusuk, amely jellemzi azokat. Ez a what.
std::exception¶
Legtöbbször a hibáknak külön osztályt hozunk létre. Ekkor ezeket az std::expection osztályból származtatjuk le. Ennek az osztálynak a lényege, hogy polimorfikusan általánosítsa a hibákat, itt található a virtuális what metódus. Mivel polimorfikusan használhatók a típusok, az előbbi példát std::exception típussal is elkaphattuk volna:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Látható, hogy így is az 'stoi' üzenetet kapjuk, hiszen még mindig az std::invalid_argument hiba dobódott, csak polimorfikusan kezeltük.
Referencia használata nélkül azonban csak az 'std::exeption' üzenetet kapnánk, hiszen a polimorfizmus nem működik érték szerinti átadás során. ROSSZ HASZNÁLAT:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
warning: catching polymorphic type ‘const class std::exception’ by value [-Wcatch-value=] catch(const std::exception error) {
Több catch ág¶
Több catch feltételt is írhatunk a try ághoz, hiszen több féle hiba is felléphet egy-egy művelet során. Ekkor ezek lineárisan kerülnek tesztelésre, és fontos, hogy az eddig tanultakhoz hasonlóan az első illeszkedő kivételkezelő fogja kezeli a hibát (nem pedig a legjobban illeszkedő, tehát ha egyszer kapunk egy gyerek típusú hibát, de az őst elkapó kivételkezelő van a kódban előrébb, akkor minden esetben az ősé fog lefutni).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Látható, hogy a második catch ágban folytatódik a program futása, hiszen az std::invalid_argument exception polimorfikusan sem tudja kezelni az éppen eldobott std::out_of_range exceptiont. Ekkor továbbhalad a hiba a következő catch ágra, ami már tudja kezelni, így onnan nem halad tovább.
Látható, hogy felkészültünk egyéb hibák kezelésére is, nem csak a két konverziós hibára. Ha ezt rossz helyre írjuk, akkor információt veszíthetünk. Ha előre írnánk, akkor minden hibát polimorfikusan elkapna az std::exception így a specifikus catch ágakba sosem kerülne a vezérlés.
ROSSZ PÉLDA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
warning: by earlier handler for ‘std::exception’ catch(const std::exception& error) { // ROSSZ! Minden hibat elkap polimorfikusan
Saját hiba típus¶
Saját hibát is definiálhatunk és hasonlóan általánossá tehetjuk az eddig ismert hibákhoz, ha azt az std::exception osztályból (vagy egy leszármazottjából) származtatjuk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
Az egyszeru_sajat_hiba típus csak a beépített what metódust írja felül, hogy a hiba kiíratásakor megjelenő szöveg az általunk megadott legyen (ez megoldható volna az exception példányosításával, azonban így lesz saját hibatípusunk, így saját hibakezelőt is írhatunk az általunk definiált típushoz). Mivel a sajat_hiba már általános std::exception-ként is használható csak a használata marad hátra. Nem elegendő megkonstruálnunk, el is kell dobni. Az eddigieken kívül természetesen a hiba objektumok is ugyanolyan objektumok, mint bármelyik másik, lehetnek adattagjai, különböző konstruktorai, metódusai (ahogy látjuk a gettereket is).
noexcept kulcsszó¶
A what felüldefiniálása során meg kellett adni egy noexcept nevű tulajdonságot. Ez azt jelzi, hogy a metódus biztosan nem fog hibát eredményezni, nem kerül sor újabb exception eldobására annak futása során.
throw kulcsszó¶
Ha egy folyamat végrehajtása során hibába fut a program, a programozónak kell megoldania, hogy egy hiba eldobásra kerüljön. Ezt a throw kulcsszóval tehetjük meg. Meg kell adni, hogy mi az amit el szeretnénk dobi. Ez lehet pl. a sajat_hiba egy példánya.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Nem osztály hibák¶
A hibakezelés elején említésre került, hogy általában osztálytípusúak a hibák, azonban ez C++ esetében nem feltétlenül kell így legyen (Java esetén csak így lehetett). Bármit el lehet dobni, bármilyen értéket; legyen az _const char, _std::string, int, stb. Ezeket is el lehet kapni, azonban ekkor már nem használható az exception elkapása, hiszen ezek nem leszármazottjai az std::exception* osztálynak (és nem is konkrétan exception típusúaka). Ekkor a típusukat kell használnunk. Természetesen ezeket is elkaphatjuk referencia szerint, azonban primitív típusok esetén ez nem szükséges.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Minden hiba kezelése¶
Látható, hogy egyes hibák nem tartoznak az std::exception alá, így általánosan nem kezelhetők. Azonban ha szeretnénk megoldani, hogy biztosan elkapjunk minden hibát, minden lehetséges hibatípusra kell catch ágat írnunk. Azonban ez nem biztos, hogy minden esetben lehetséges (vagy azért mert rengeteg féle-fajta hiba van, amiket azonosan akarunk kezelni, vagy pedig azért mert nem is tudjuk pontosan, hogy milyen hibákra kell felkészíteni a programunkat). Azonban ezt is megoldhatjuk, ha elkapáskor a típusok helyett egyszerűen csak ...
(három pontot) írunk. Ezzel bármit el tudunk kapni a catch ágban.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
RAII (Resource acquisition is initialization)¶
Ez a fogalom annyit jelent, hogy az erőforráso foglalás az inicializálás során történik meg. A név ha nem is a legegyértelműbb, széles körben elterjedt.
Használata a hibakezeléshez köthető, méghozzá erőforrás használatakor. Legyen erre példa a következő:
-
try ágban foglaljunk erőforrást
-
hajtsuk végre a try ágat
-
exception dobódik
-
catch ág kezeli a hibát
Ekkor azonban a catch ágra ugrik azonnal a vezérléás, több esetén csakis az egyikre. Ha a try blokk végén volt erőforrás felszabadítás, az nem futott le. Javaban erre a megoldás a finally ág volt, mely minden esetben, hibátlan esetleg hibás (bármilyen) lefutás esetén végrehajtásra került; ezzel biztosítva az erőforrás felszabadítását. Ilyen C++ esetében nincsen, azonban pont erre megoldás a RAII és a feltételezés, hogy az erőforrások objektumok, melyek konstruktorral kerülnek inicializálásra és fontosabb, hogy destruktor hívódik megszűnésükkor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
Látható, hogy a try blokkban erőforrást egy objektum létrehozásával foglalunk, majd használjuk, de a felszabadítás előtt hiba exception dobódik. Ennek ellenére nincsen szükség finally ágra, hiszen a destruktor láthatóan így is lefutott. Mivel a try blokkot elhagytuk, a catch ágba kerültünk, így az objektum megszűnt, ami a destruktor lefutását eredményezte.