17. října 2011, Brno Připravil: David Procházka Práce s výjimkami Programovací jazyk C++
Obecně Strana 2 / 21 Jak se může program zachovat při chybě Dříve byl obvyklý způsob zavolat metodu abort. Metoda okamžitě vyprázdnila vyrovnávací pamět, ukončila celý program, vrátila číslo nadřazeného procesu, vypsala na chybový výstup hlášku o ukončení. Alternativně se využívala metoda exit. Nejedná se o elegantní řešení, proto byl preferován mechanismus návratových hodnot. Metoda v tomto případě neukončuje program, jen signalizuje chybu. Návratovou hodnotu je však možné ignorovat. Návratová hodnota nenese obvykle komplexnější informace o chybě.
Obecně Strana 3 / 21 Výjimky Vyjímka představuje zajímavý způsob, jak signalizovat programu, že v určité entitě nastala chyba. Řeší problémy zmíněné u návratových hodnot. Výjimky používá řada funkcí/metod (přístup pomocí metody at() na místo v řetězci). Je vyvolána pomocí příkazu throw. Hodnotou, která je jako vyjímka vyhozena může být jednoduchý datový typ, obvykle používáme objekty. Výjimky si můžeme nadefinovat sami, použít standardní výjimky a výjimky od nich odvozené (doporučeno). Výjimka je odchytávána v bloku kódu označeném jako try. Řešena je v bloku catch(). Užití výjimek výrazně zpomaluje program.
Obecně Strana 4 / 21 Implementace 1 try { 2 // pokus o operaci potencialne vyhazujici vyj. 3 } catch ( myexception &e) { 4 // osetreni neplatneho pokusu 5 } V bloku catch je uveden odkaz na výjimku. Ve skutečnosti se však jedná o odkaz na kopii. Původní výjimka obvykle v době zpracování již neexistuje.
Obecně Strana 5 / 21 Příklad ošetření výjimky u metody at() Dojde-li v bloku try k vyvolání výjimky, běh programu se přeruší a přejde se k nejbližšímu handleru výjimek catch. 1 # include < exception > 2 # include < string > 3 4 int main () { 5 std :: string s(" 1234 "); 6 try { 7 s.at (5); 8 std :: cout << " toto se nikdy nevypise "; 9 } catch ( exception &e) { 10 std :: cerr << " Nastala chyba " << std :: endl ; 11 std :: cerr << e. what () << std :: endl ; 12 } 13...
Vytváření výjimek Strana 6 / 21 Příklad Vytvořte třídu reprezentující zlomek, který obsahuje atributy citatel a jmenovatel. Dále má metodu vyděl, která vyhazuje výjimku v případě dělení nulou. Výjimku reprezentujte vlastní třídou obsahující popis problému (lze rozšířit o další informace hodnoty čitatele a jmenovatele, atp.). 1 class Vyjimka { 2 private : 3 std :: string m_popis ; 4 public : 5 void Vyjimka ( std :: string d) { m_ popis = d; } 6 std :: string vrat () { return m_ popis ; } 7 };
Vytváření výjimek Strana 7 / 21 Ruční vyhození výjimek Pokud má mít některá funkce možnost vyvolat výjimku, musí to být uvedeno v deklaraci funkce (metody). Pokud může funkce vracet výce typů výjimek, musí být v závorce všechny. 1 class Zlomek { 2 private : 3 int m_ citatel, m_ jmenovatel ; 4 public : 5 Zlomek ( int citatel, jmenovatel ){ 6 m_ citatel = citatel ; 7 m_ jmenovatel = jmenovatel ; 8 } 9 double vydel () throw ( Vyjimka ); 10 };
Vytváření výjimek Strana 8 / 21 Implementace metody vydel() 1 double Zlomek :: vydel () throw ( Vyjimka ){ 2 if ( m_ jmenovatel == 0) { 3 Vyjimka v(" Deleni nulou ve zlomku "); 4 throw v; 5 // lze vyhodit i nepojmenovanou instanci tridy 6 // throw Vyjimka (" Popis problemu "); 7 } 8 return (( double ) m_citatel / m_jmenovatel ); 9 } Je důrazně doporučováno vyhazovat výjimky hodnotou a odchytávat odkazem.
Vytváření výjimek Strana 9 / 21 Jak výjimku zpracujeme? 1 Zlomek * z = new Zlomek (10,0); 2 3 try { 4 std :: cout << " 10/0= " << z-> vydel () << std :: endl ; 5 std :: cout << " Pokud je delitel!= 0 vypise se to"; 6 } catch ( Vyjimka & v) { // pokud je vyhozena Vyjimka 7 std :: cout << v. vrat () << std :: endl ; 8 } catch ( jinavyjimka &j) { // pokud jina 9... 10 // opetovne uvolneni vyjimky pro zpracovani 11 // specialni pripad, bezne nedelame! 12 throw ; 13 }
Vytváření výjimek Strana 10 / 21 Odchycení všech výjimek V závorce jsou opravdu tři tečky. 1 try { 2 // vyvolani vyjimky 3 } catch (...) { 4 // osetreni 5 } Nelze však identifikovat typ výjimky druh problému.
Speciální případy Strana 11 / 21 Ošetření zrušení objektu speciální případ! 1 // někde v metodě 2 Trida * instance = new Trida ; 3 4... 5 // kod, ktery potencialne vyhodi vyjimku 6 // tato vyjimka je vyslana mimo metodu 7... 8 9 delete instance ; 10... Pokud bude vyhozena výjimka, objekt se nikdy nezruší.
Speciální případy Strana 12 / 21 Ošetření zrušení objektu řešení 1 Trida * instance = new Trida ; 2 3 try { 4 // kod, ktery potencialne vyhazuje vyjimku 5 } catch ( exception &e) { 6 7 delete instance ; 8 //" pozastavenou " vyjimku je treba zase uvolnit 9 throw ; 10 } a) Nelze to ošetřit podmínkou? b) Metoda nesmí výjimku odchytit a zrušit bez toho, že by problém vyřešila. Pokud problém který vyhození způsobil setrvává, musí být výjimka (znovu) vypuštěna.
Hierarchie výjimek Strana 13 / 21 Vytváření výjimek a dědičnost 1 class Vyjimka { 2 private : 3 std :: string m_popis ; 4 public : 5 Vyjimka ( std :: string popis ){ m_ popis = popis ;} 6 std :: string vratpopis () { return m_ popis ; } 7 }; 8 9 class DeleniNulou : public Vyjimka { 10 private : 11 int m_ cislo ; 12 public : 13 DeleniNulou ( std :: string t, int c): Vyjimka (t){ 14 m_cislo =c; 15 } 16 int vratcislo () { return m_ cislo ; } 17 };
Hierarchie výjimek Strana 14 / 21 Řešení jejich odchycení již bylo zmíněno 1 try { 2 // vyvolani v. 3 } catch ( DeleniNulou & v) { 4 // osetreni 5 } catch ( Vyjimka & v) { 6 // osetreni 7 } Je nutné dávat si pozor na pořadí v jakém výjimky odchytáváme (potomek může vystupovat v roli předka!). Pokud nejdříve odchytáváte předka, odchytí se i potomek!
Standardní výjimky Strana 15 / 21 Standardní výjimky Spíše, než definovat si vlastní výjimky, je vhodné využívat standardních výjimek, případně odvozené. Výjimky jsou odvozeny od třídy exception. Tato třída obsahuje virtuální metodu what(). Z ní jsou odvozené logic error (chyby v logice programu) a runtime error (chyby zjistitelné až za běhu programu). Abychom mohli používat standardní výjimky, je nutné načíst stdexcept. Pokud uživateli nevyhovuje žádná ze standardních výjimek, je vhodné odvodit vlastní z této hierarchie. Proto je vhodné, aby se programátor nejdříve dobře seznámil se standardními výjimkami.
Standardní výjimky Strana 16 / 21 Standardní výjimky Z logic error se odvozují: domain error asinu je předávána hodnota mimo interval -1, 1, invalid argument funkci byla předána neočekávaná hodnota, length error pro požadovanou operaci není dostatek místa (append), out of bounds obvykle špatný index. Z runtime error se odvozují: underflow error pokud při výpočtech v plovoucí čárce vyjde číslo, které je menší, než minimální zobrazitelné, overflow error výsledek číslo je větší, než maximální zobrazitelné, range error výsledek je mimo obor hodnot. A znich zase další...
Standardní výjimky Strana 17 / 21 Úkol Zkuste se zamyslet nad příkladem se zlomkem a upravit jej tak, aby podporoval některou z hierarchie standardních výjimek. Dále se pokuste vytvořit vlastní a zařadit ji do hierarchie.
Standardní výjimky Strana 18 / 21 Příklad standardní výjimky 1 Trida * objekt ; 2 try { 3 objekt = new Trida ; 4 } catch ( std :: bad_alloc &b) { 5 cout << " Vyhodil výjimku " << endl ; 6 } nebo vyvolání výjimky lze zamezit a provést ruční ošetření: 1 objekt = new ( nothrow ) Trida ; 2 if ( objekt == 0) 3 cout << " nepovedlo se zaalokovat pamet " << endl ; 4 }
Neočekávané chování Strana 19 / 21 Ošetření neodchycené výjimky Zavolá se funkce terminate. Ta pokud není řečeno jinak, zavolá abort. Toto chování lze modifikovat pomocí set terminate: 1 void mojeterminate () { 2 cerr << " Neodchycena vyjimka " << endl ; 3 // radeji vzdy ukoncit program 4 } 5 6 int main () { 7 set_ terminate ( mojeterminate ); 8 throw 1; 9 cout << " Nestane se" << endl ; 10 return 0; 11 }
Neočekávané chování Strana 20 / 21 Ošetření neočekávané výjimky Neočekávaná výjimka je taková, která je vyhozena funkcí, přestože není uvedena v její hlavičce. Zavolá se funkce unexpected. Ta pokud není řečeno jinak, zavolá terminate. Toto chování lze modifikovat pomocí set unexpected: 1 void mojeunexp () { 2 cerr <<" Neocekavana chyba " << endl ; 3 exit ( -10); // radeji vzdy ukoncit program 4 } 5 6 int main () { 7 set_ unexpected ( mojeunexp ); 8... 9 }
Neočekávané chování Strana 21 / 21 Poznámka Výjimky nenahrazují zcela používání návratových hodnot funkcí/metod jako indikátoru chybového stavu. Jedná se o určitý komplexní doplněk, který umožňuje lépe předávat informace o tom co se vlastně stalo a lépe také řešit situace, kde může dojít řadě různých chyb (najednou).