2009.
Kivételek, kivételkezelés a C++ nyelvben Haladó C++ programozás
Kurucz Attila ELTE - IK 2009.06.09.
Tartalomjegyzék Tartalomjegyzék ..................................................................................................................... 2 Mi a kivételkezelés? ................................................................................................................ 3 Alapfogalmak .......................................................................................................................... 3 Kivétel.................................................................................................................................. 3 Try blokk .............................................................................................................................. 3 Catch blokk .......................................................................................................................... 3 Hagyományos hibakezelés .................................................................................................. 4 Kivételkezelés céljai ................................................................................................................ 4 Működés ................................................................................................................................. 4 Egymásba ágyazott kivételkezelők...................................................................................... 5 Leszármaztatott kivételosztályok........................................................................................ 5 Az általános catch blokk ...................................................................................................... 5 Függvény kivételek .............................................................................................................. 5 Kivétel a konstruktorban ........................................................................................................ 6 Kivétel a destruktorban .......................................................................................................... 6 Erőforrás foglalás felszabadítás .............................................................................................. 6 Szabványos kivételek .............................................................................................................. 7 Számológép példa ................................................................................................................... 8 Források .................................................................................................................................. 9
2
Mi a kivételkezelés? A kivételkezelés segítségével vel kezelni tudjuk a futási időben történő hibákat. Kettő darab igen egyszerű lépés szükséges a dologhoz, elsőként kijelöljük azt a programrészt ami „dobhat” kivételt, másodszor pedig „elkapjuk” azt. A kivételkezelés a C++-ban ban voltaképp hibakezelést jelent.. Ehhez három új nyelvi alapszót vezettek be: try, catch, throw. throw
Alapfogalmak Kivétel Nem azonos az operációs rendszer exception fogalmával. Ez alól kivétel az, amit a programozó annak tekint. A C++ szemléletében a kivétel egy objektum, ami a kivétel bekövetkezésekor jön létre. Kivétel kiváltása: throw. throw. Azt nevezzük kivételnek, kivételnek mire alapesetben nem számítunk, mint min például elfogy a hely a winchesteren. Try blokk A „védett” programrészt fogja közre. try { utasítás1; utasítás2; … utasításn; } Catch blokk A keletkezett kivételek lekezelése itt történik. A try blokkot mindig legalább egy catch blokknak követnie kell, de követheti több is. Mindegyik catch ág specifikálja, hogy milyen 3
típusú kivételeket kezel le. A megfelelő catch ágat a dobott kivétel típusával összevetve választja ki a rendszer. catch (típus paraméter) { utasítás1; utasítás2; … utasításn; } Hagyományos hibakezelés Függvény visszatérési értéke (paramétere) hátrányai: • azonosítás • hibaérték / valódi érték • hívási hierarchia • ellenőrzés nehézkes, sok helyre kell beiktatni • kevésbé átlátható kód
Kivételkezelés céljai • • • • • •
kivételek csoportosítása együttműködik más nyelvekkel (C) működik párhuzamos környezetben minden kivételt a megfelelő kezelő kapja el ne legyen extra kód/hely/idő ha nem használjuk típus-biztos tetszőleges adat továbbítás a kivétel létrejöttének helyétől a lekezelés helyhez
Működés Az első throw utasítással befejeződik a try blokk végrehajtása. Kilép a blokkból a vezérlés és rendet csinál: verem visszaállítás, lokális objektumok megszüntetése. Létrejön a throw utasításban megjelölt objektum egy példánya. Megkeresi a program azt az első catch blokkot, amely a kivétel objektumra illeszkedik és végrehajtja a blokk utasításait. A többi ágat nem vizsgálja meg, még akkor sem, ha ott esetleg tökéletes egyezést találna, ezért a catch ágak sorrendje nem közömbös. A catch blokk végrehajtása után az utolsó catch blokk utáni utasításra kerül a vezérlés. Ha kivétel keletkezik, de egy catch blokk sem illeszkedik akkor a terminate függvény hívódik meg, azaz leáll a program.
4
Egymásba ágyazott kivételkezelők A try blokkok egymásba ágyazhatóak explicit vagy implicit módon. A catch blokkok keresése „belülről kifelé” történik. Ha verem visszagombolyítás közben újabb kivétel keletkezik, akkor a terminate függvény hívódik meg. A kivétel lekezelése (vagy annak egy része) továbbhárítható a feljebb álló szintekre a paraméter nélküli throw utasítással. Leszármaztatott kivételosztályok Mivel a kivétel egy objektum, ezért a kivétel osztályok között lehet egy leszármazási hierarchiát felállítani, így csoportosítani a kivételek. Egy E kivételobjektum illeszkedik a catch(H) blokkra, ha: • H és E típusa azonos • H egy egyértelmű bázisosztálya E-nek • H és E pointerek és alaptípusokra a fenti érvényes • H egy referencia, és a hivatkozott típusra az első két pont valamelyike érvényes Az általános catch blokk catch ( ... ) { utasítás1; utasítás2; … utasításn; } Erre a blokkra bármely kivétel objektum illeszkedik, tehát logikusan csak az utolsó lehet a blokkok sorában. Használható arra, hogy minden egyéb speciális lekezelést nem igénylő kivételt itt kezeljünk le. Függvény kivételek Egy függvény esetében megadható az, hogy a függvény milyen kivételeket tud generálni: void f(int x) throw(A,B,C); void g() throw(); void h();
// csak A,B,C kivételek // semmi // bármi
Eszerint az f függvény csak az A,B és C típusú kivételt generál, vagy azok leszármazottait. Minden más esetben a rendszer meghívja az unexpected() függvényt, melynek alapértelmezett viselkedése a terminate() függvény meghívása. A catch ág lefutása után a try blokk után folytatódik a program végrehajtása.
5
Kivétel a konstruktorban Lényegében a kivételkezelés az egyetlen mód arra, hogy a konstruktor hibát jelezzen. Hiba esetén gondoskodni kell a megfelelő objektum állapot előállításáról. Inicializáló listán keletkező kivétel elfogása: class A { B b; public: A() try : b() { // konstruktor programozott része } catch (...) { // kivételkezelés } };
Kivétel a destruktorban Destruktor hívás oka: 1. normál meghívás 2. kivételkezelés (roll back) miatti meghívás. Ekkor a kivétel nem léphet ki a destruktorból. Destruktorban keletkező kivétel elfogása: A::~A() try { // destruktor törzse } catch (...) { // kivételkezelés }
Erőforrás foglalás felszabadítás Gyakori, hogy erőforrásként kezelünk valamit. Pl: memória, fájl, eszköz stb. Erőforrás használatának sorrendje: lefoglalás, feldolgozás, felszabadítás. Ügyelni kell arra, hogy
6
feldolgozás közben észlelt hiba esetén is fel kell szabadítanunk az erőforrást. FILE * fp = fopen("x.txt", "r"); try { // file feldolgozás } catch (...) { fclose(fp); throw; } fclose(fp);
Szabványos kivételek bad_alloc bad_cast bad_typeid bad_exception ios_base::failure
exception
range_error overflow_error underflow_error
runtime_error
length_error domain_error out_of_range invalid_argument
logic_error
7
Számológép példa A négy alapműveletet fogja elvégezni és leellenőrzi, hogy jó műveleti jelet adtunk-e meg és, hogy ne osszunk nullával. void f(int x) throw(A,B,C); #include
enum Exceptions { DIVNULL, BAD_OPERATOR }; double calc(double lhs, double rhs, char op) throw(Exceptions) { switch(op) { case '+': return (lhs + rhs); case '-': return (lhs - rhs); case '*': return (lhs * rhs); case '/': if(rhs == 0.0) throw Exceptions::DIVNULL; return (lhs / rhs); default: throw Exceptions::BAD_OPERATOR; } } int main(int argc, char *argv[]) { try { //Ez jó std::cout << calc(1.0, 0.5, '+') << std::endl; //Ez kivételt okoz std::cout << calc(1.0, 0.0, '/') << std::endl; } catch(Exceptions e) { if(e == Exceptions::BAD_OPERATOR) std::cout <<"Rossz operator!\n"; else std::cout << "Nullaval valo osztas nem engedelyezett!\n"; } return 0; }
8
Források • • • • • • •
ELTE - Haladó C++ programozás előadás diái: http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02.html http://digitus.itk.ppke.hu/~szalka/prognyelv/gyakorlat6.htm Miskolci Egyetem – Kivételkezelés a C++ nyelvben előadás diái: http://users.iit.uni-miskolc.hu/ficsor/segedlet/cpp9hand.pdf BME – Programozás alapjai II. C++ 10. előadás diái: http://www.fsz.bme.hu/~szebi/slides/cpp9_bsc_6.pdf Alexandrescu – Choose Your Poison: Exceptions or Error Codes? http://accu.org/content/conf2007/Alexandrescu-Choose_Your_Poison.pdf Wikipédia: http://hu.wikipedia.org/wiki/C%2B%2B#Kiv.C3.A9telkezel.C3.A9s http://www.progtut.net/index.php?p=Article&id=108
9