160 likes | 282 Views
Nyugat-Magyarországi Egyetem Faipari Mérnöki Kar Informatikai Intézet Soós Sándor 2004. december. C++ programozási nyelv Gyakorlat - 11. hét. Tartalomjegyzék. Mit nevezünk kivételkezelésnek? Miért fontos ez? Mi a megoldás? Miért nem jó ez a megoldás? A kivételkezelés ötlete
E N D
Nyugat-Magyarországi Egyetem Faipari Mérnöki Kar Informatikai Intézet Soós Sándor 2004. december C++ programozási nyelvGyakorlat - 11. hét
Tartalomjegyzék • Mit nevezünk kivételkezelésnek? • Miért fontos ez? • Mi a megoldás? • Miért nem jó ez a megoldás? • A kivételkezelés ötlete • Kivétel keletkezése • A kivétel addig terjed, míg el nem kapjuk • Kivételt szándékosan is létre lehet hozni ('dobás') • A kivétel típusát meg tudjuk állapítani • A kivétel értékét is használhatjuk • A függvényeket ugyanúgy hagyja el a program, mint normális esetben • A kivétel továbbdobható • Transzformálás 1: Elkapás után másik kivétel dobható tovább • Transzformálás 2: Elkapás után hibakódot adunk vissza
Mit nevezünk kivételkezelésnek? • Szűkebb értelemben: • A programban fellépő futásidejű hibák kezelését. • Tágabb értelemben: • A program normál logikájához képest különleges, attól eltérő helyzetek kezelését. • Ez azt is jelenti, hogy abszolút nem hibás esetek kezelését is megoldhatjuk a kivételkezelés mechanizmusával. • A végső cél: • Teljes egészében kezünkben tartsuk a program működését, minden helyzetben a szándékaink szerint reagáljon a program. Soha ne forduljon elő olyan eset, amikor a program úgy fejeződik be, hogy valamit "elvarratlanul" hagyott.
Miért fontos ez? • Milyen problémákat okozhat egy rendkívüli program leállás? • adatvesztés: • nyitva maradt fájlok • még el nem mentett, berögzített adat. A felhasználó begépelt 5 oldalnyi szöveget, amikor a program "elszáll", vagy hibajelzéssel leáll. • időveszteség: • egy 5 órája futó kalkuláció a végeredmény elkészülte és a részeredmények elmentése nélkül hibajelzéssel leáll. • erőforrások fogvatartása • a program futása során lefoglal bizonyos erőforrásokat (fájlok, adatbázis rekordok), ezeket a futás végén szabadítaná fel.
Mi a megoldás? • A programot fel kell készíteni minden eshetőségre. • Például: Az előző program hibakezeléssel: program { if (fuggveny1() = OK) { if (fuggveny2() = OK) { if (fuggveny3() = OK) { // minden rendben } else { // hibakezelés3 } } else { // hibakezelés2 } } else { // hibakezelés1 } } Egy egyszerű program: program { fuggveny1(); fuggveny2(); fuggveny3(); } Miért nem jó ez?
Miért nem jó ez a megoldás? • A hibakezelés elrejti, széttördeli az eredeti programlogikát: • nehezen olvasható kód • nehezen javítható, módosítható a kód • A hibakezelő programrésznek nincs információja a hiba jellegéről, okáról • Nagyobb programok esetén ez a megoldás kivitelezhetetlen, sőt... • hiba bekövetkezhet a hibakezelő programrészben is, ezért oda is kellene ellenőrzés, ...
A kivételkezelés ötlete • Válasszuk szét az eredeti programlogikát és a kivételes helyzetek (hibaállapotok) kezelését • A kivételkezelő kapjon információt a hiba típusáról, helyéről és egyéb körülményeiről • A kivételkezelő legyen képes visszatérni az eredeti programlogikához, ha a hiba jellege ezt lehetővé teszi. • Legyen lehetőség garantálni a program és minden részprogram korrekt befejeződését bármilyen körülmények fennállása esetén (fájlok bezárása, erőforrások felszabadítása, stb.)
Kivétel keletkezése int main() // except_bem1.cpp { int x,y; try{ y = 0; x = 1 / y; } catch(...){ // minden kivételt elkapunk printf("Kivetel dobasa tortent\n"); } return 0; } • try nélkül, debug indításával: a hibaüzenet az, hogy lekezeletlen kivételt kapott el a futtató rendszer, mégpedig a nullával osztáshoz tartozót. • A kivételt létrehozhatja a futtató rendszer, vagy mi magunk.
A kivétel addig terjed, míg el nem kapjuk int main() { try{ KiveteltDob(); } catch(...){ // minden kivételt elkapunk printf("Kivetel dobasa tortent\n"); } try{ F(); } catch(...){ // minden kivételt elkapunk printf("Kivetel dobasa tortent\n"); } return 0; } // except_bem2.cpp void KiveteltDob() { int x,y; y = 0; x = 1 / y; } void F() { KiveteltDob(); }
Kivételt szándékosan is létre lehet hozni ('dobás') void Dob1(int X) { if(X == 0) throw 122; } // except_bem3.cpp void Dob2(int X) { if(X == 0) throw "Baj van!"; } int main() { try{ Dob1(0); } catch(...){ // minden kivételt elkapunk printf("Kivetel dobasa tortent\n"); } try{ Dob2(0); } catch(...){ // minden kivételt elkapunk printf("Kivetel dobasa tortent\n"); } return 0; } • Megjegyzés: Sztringet dobni nem nagyon érdemes, a gyakorlatban kevéssé használható (ld. később).
A kivétel típusát meg tudjuk állapítani void Dob1(int X) { if(X == 0) throw 122; } // except_bem4.cpp void Dob2(int X) { if(X == 0) throw "Baj van!"; } int main() { try{ Dob1(0); } catch(int){ printf("int kivetel dobasa tortent\n"); } catch(...){ // minden egyéb kivételt elkapunk printf("Egyeb kivetel dobasa tortent\n"); } try{ Dob2(0); } catch(char*){ printf("char* kivetel dobasa tortent\n"); } catch(...){ // minden egyéb kivételt elkapunk printf("Egyeb kivetel dobasa tortent\n"); } return 0; }
A kivétel értékét is használhatjuk void Dob1(int X) { if(X == 0) throw 122; } // except_bem5.cpp void Dob2(int X) { if(X == 0) throw "Baj van!"; } int main() { try{ Dob1(0); } catch(int k) { printf("int kivetel dobasa tortent: %d\n",k); } catch(...) { // minden egyéb kivételt elkapunk printf("Egyeb kivetel dobasa tortent\n"); } try{ Dob2(0); } catch(char* k) { printf("char* kivetel dobasa tortent: %s\n",k); } catch(...) { // minden egyéb kivételt elkapunk printf("Egyeb kivetel dobasa tortent\n"); } return 0; }
A függvényeket ugyanúgy hagyja el a program, mint normális esetben class A{ public: A() { printf("A::A()\n"); } ~A() { printf("~A::A()\n"); } }; void f(int X) { A a; if(X == 1){ printf("Kivetelt dobok\n"); throw 1; } printf("Nem dobtam kivetelt\n"); } void main() { f(0); printf("-------\n"); try{ f(1); } catch(...) {} printf("Mindig lefut A::~A()!\n"); } • Persze ami nincs objektumba csomagolva, vagy dinamikus objektumban van, az nem záródik le! • Lásd except_bem7.cpp !
A kivétel továbbdobható void F1() { throw 42; } // except_bem8.cpp void F2() { try{ F1(); } catch(int x){ printf("F2: elkaptam: %d\n",x); printf("Feldolgoztam, tovabbdobom\n"); throw; } } int main() { try{ F2(); } catch(int x){ printf("main: elkaptam: %d\n",x); } return 0; }
Transzformálás 1: Elkapás után másik kivétel dobható tovább • Ez pl. arra használható, hogy a kapott sokféle kivétel információit felírjuk (log), majd kevés féle kivételt adunk tovább. A feldolgozás után már csak nagyvonalúbb leírásra van szükség, a részletek csak zavarnak. Ez a következő pontra is érvényes. #define VALAMI_NAGY_BAJ -1 void F1(int X) { if(X < -1000) throw "Nagy baj 1"; if(X < -100) throw "Nagy baj 2"; if(X < -10) throw "Nagy baj 3"; } void F2() { try{ F1(-777); } catch(char* x){ printf("F2: elkaptam: %s\n",x); printf("Feldolgoztam, tovabbdobom\n"); throw VALAMI_NAGY_BAJ; } } // except_bem9a.cpp int main() { try{ F2(); } catch(int x){ printf("main: elkaptam: %d\n",x); if(x == VALAMI_NAGY_BAJ) printf("Valami nagy baj tortent\n"); } return 0; }
Transzformálás 2: Elkapás után hibakódot adunk vissza // except_bem9b.cpp int main() { int rc; rc = F2(); printf("main: F2 visszatero erteke: %d\n",rc); if(rc == VALAMI_NAGY_BAJ) printf("Valami nagy baj tortent\n"); return 0; } #define VALAMI_NAGY_BAJ -1 void F1(int X) { if(X < -1000) throw "Nagy baj 1"; if(X < -100) throw "Nagy baj 2"; if(X < -10) throw "Nagy baj 3"; } int F2() { try{ F1(-777); } catch(char* x){ printf("F2: elkaptam: %s\n",x); printf("Feldolgoztam\n"); return VALAMI_NAGY_BAJ; } return 0; }