Content extract
2009. Kivételek, kivételkezelés a C++ nyelvben Haladó C++ programozás Kurucz Attila ELTE - IK 2009.0609 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 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 voltaképp hibakezelést jelent. Ehhez három új nyelvi alapszót
vezettek be: try, catch, 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, mire alapesetben nem számítunk, mint 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 <iostream> 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, 05, +)
<< std::endl; //Ez kivételt okoz std::cout << calc(1.0, 00, /) << std::endl; } catch(Exceptions e) { if(e == Exceptions::BAD OPERATOR) std::cout <<"Rossz operator! "; else std::cout << "Nullaval valo osztas nem engedelyezett! "; } return 0; } 8 Források • • • • • • • ELTE - Haladó C++ programozás előadás diái: http://aszt.infeltehu/~gsd/halado cpp/ch02html http://digitus.itkppkehu/~szalka/prognyelv/gyakorlat6htm Miskolci Egyetem – Kivételkezelés a C++ nyelvben előadás diái: http://users.iituni-miskolchu/ficsor/segedlet/cpp9handpdf BME – Programozás alapjai II. C++ 10 előadás diái: http://www.fszbmehu/~szebi/slides/cpp9 bsc 6pdf Alexandrescu – Choose Your Poison: Exceptions or Error Codes? http://accu.org/content/conf2007/Alexandrescu-Choose Your Poisonpdf Wikipédia: http://hu.wikipediaorg/wiki/C%2B%2B#KivC3A9telkezelC3A9s http://www.progtutnet/indexphp?p=Article&id=108 9