Presentation is loading. Please wait.

Presentation is loading. Please wait.

Obsluha výnimiek.

Similar presentations


Presentation on theme: "Obsluha výnimiek."— Presentation transcript:

1 Obsluha výnimiek

2 Osnova prednášky Chyby a výnimky Spracovanie výnimiek
Výnimky pri alokácii pamäte Výnimky v konštruktoroch

3 Chyby a výnimky Pri vývoji a používaní programu sa môže vyskytnúť niekoľko druhov problémov: 1. Chyby v kóde – tieto chyby sa zachytia už v štádiu kompilácie, to znamená, že ak sa v programe vyskytujú, program sa ani nespustí 2. Logické chyby – program sa spustí, ale kvôli chybe programátora nefunguje správne 3. Výnimky – sú to problémy, ktoré nastanú počas behu programu a vznikajú v dôsledku vonkajších okolností nevhodných dát (delenie nulou, pokus o čítanie neexistujúceho súboru..), hardvérových problémov (nedostatok pamäte, poškodený hardvér) a podobne Zatiaľ čo chyby (v kóde a logické) musí programátor odstrániť, výnimky môže len predvídať a môže zahrnúť do programu príkazy, ktoré sa vykonajú, ak výnimka nastane

4 Spracovanie výnimky Spracovanie výnimky v C++ je založené na troch kľúčových slovách: try, throw a catch (skús, oznám a zachyť) Blok try – obsahuje časť programu, v ktorej sa môže vyskytnúť výnimka. Môžeme ho chápať tak, že program skúsi danú časť programu vykonať a zisťuje, či pri tom nastane výnimka alebo nie. Príkaz throw – oznámi, že nastala výnimka (a aká). Ak sa nachádza priamo v bloku try, vykonávanie príkazov v bloku sa končí, ak sa nachádza vo vnútri nejakej funkcie, program z funkcie odchádza. Dôležitou vlastnosťou tohto príkazu je, že zabezpečuje korektný odchod z bloku alebo funkcie - zruší lokálne premenné, ktoré boli vytvorené, a zavolá deštruktory objektov. Funkcia catch – táto funkcia zachytí výnimku určitého typu. V jej tele je uvedené, čo sa má spraviť, ak daná výnimka nastala - môže ukončiť program, korektne obísť problém, alebo skúsiť napraviť chybu.

5 Spracovanie výnimky Syntax mechanizmu try-throw-catch je nasledujúca:
{ ... ak nastala výnimka throw výnimka; } catch(typ_výnimky výnimka) Samozrejme, v bloku try môže nastať niekoľko výnimiek, rovnakého alebo rôzneho typu. Takisto príkazov catch môže byť za blokom try ľubovoľne veľa, jeden pre každý možný typ výnimky. Príkaz throw musí byť spustený buď z bloku try alebo z nejakej funkcie, ktorá sa volá v tomto bloku. Výnimka môže byť premenná ľubovoľného dátového typu.

6 Spracovanie výnimky Príklad: Delenie komplexných čísel
Pokúsime sa vydeliť dve komplexné čísla. V prípade, že nastane delenie nulou, program ohlási výnimku. Definujeme konštantu, ktorá bude indikátorom výnimky: #define Delenie_nulou 1 Hlavný program: KomplexneCislo x(1,2), y(3,-1),z; cout<<"Komplexne cisla: "; x.Vypis(); cout<<", "; y.Vypis(); try { if (y.A()==0 && y.B()==0) throw Delenie_nulou; z.ZmenA((x.A()*y.A()+x.B()*y.B())/(y.A()*y.A()+y.B()*y.B())); z.ZmenB((x.B()*y.A()-x.A()*y.B())/(y.A()*y.A()+y.B()*y.B())); }

7 Spracovanie výnimky Hneď za blokom try výnimku zachytíme:
catch(int vynimka) { if (vynimka==Delenie_nulou) cout<<"Nulou delit neviem!"<<endl; return 1; } cout<<"Podiel tychto cisel: "; z.Vypis(); Pri daných číslach dostaneme výstup: Komplexne cisla: 1+2i, 3-1i Podiel tychto cisel: i Ak by sme za y zvolili nulu: KomplexneCislo x(1,2), y(0,0), z; dostali by sme výstup: Komplexne cisla: 1+2i, 0+0i Nulou delit neviem!

8 Spracovanie výnimky Blok try sa v niečom podobá na funkciu – ak sa v bloku vytvoria nejaké lokálne premenné alebo objekty, zaniknú, ak program opustí blok (ak príde na jeho koniec, alebo sa vykoná príkaz throw). Príklad: Delenie komplexných čísel a zánik objektov Do triedy KomplexnéČíslo doplníme deštruktor, ktorý nám oznámi, ktoré komplexné číslo sa ruší. KomplexneCislo::~KomplexneCislo() { cout<<"Rusim komplexne cislo "; Vypis(); cout<<endl; }

9 Spracovanie výnimky V hlavnom programe vytvoríme a vypíšeme výsledok delenia v bloku try: KomplexneCislo x(1,2), y(3,-1); ... try { KomplexneCislo z; if (y.A()==0 && y.B()==0) throw Delenie_nulou; z.ZmenA((x.A()*y.A()+x.B()*y.B())/(y.A()*y.A()+y.B()*y.B())); z.ZmenB((x.B()*y.A()-x.A()*y.B())/(y.A()*y.A()+y.B()*y.B())); cout<<"Podiel tychto cisel: "; z.Vypis(); } catch(int vynimka) if (vynimka==Delenie_nulou) cout<<"Nulou delit neviem!"<<endl; return 1; cout<<"Program prebehol bez problemov"<<endl

10 Spracovanie výnimky V tomto prípade prebehne všetko správne. Z výstupu vidíme, že číslo z, v ktorom sa uložil výsledok delenia, zanikne po skončení bloku try, kde aj vzniklo. Ak teraz y bude nulové, vznikne výnimka a blok try sa nedokončí. Vďaka throw sa však aj teraz zavolá deštruktor čísla z.

11 Spracovanie výnimky Ak by v programe vznikla výnimka nejakého typu a ak by za blokom try nenasledoval vhodný príkaz catch, ktorý by spracovával práve tento typ, program by zavolal systémovú funkciu terminate(). Táto funkcia by po vypísaní štandardnej chybovej hlášky ukončila program. Ukončenie programu pomocou terminate() však nie je korektné. Príklad: Delenie komplexných čísel bez vhodného príkazu catch V predchádzajúcom príklade zmeníme príkaz catch tak, aby zachytával výnimku typu float. Keďže naša výnimka Delenie_nulou je typu int, pre tento typ nebudeme mať k dispozícii príkaz catch. Nový príkaz catch bude vyzerať takto: catch(float vynimka) { if (vynimka==Delenie_nulou) cout<<"Nulou delit neviem!"<<endl; return 1; }

12 Spracovanie výnimky Dostaneme takýto výstup:
Vidíme, že takéto ukončenie nielen nevyzerá dobre, ale nie je ani korektné, keďže sa nezavolal ani jeden deštruktor. Každý príkaz throw musí mať zodpovedajúci príkaz catch!

13 vykonávanie nasledujúcich
Spracovanie výnimky Postup pri zachytávaní a spracovaní výnimiek možno znázorniť takto: vstup do bloku try vykonávanie príkazov bloku nenastala výnimka preskočenie všetkých príkazov catch vykonávanie nasledujúcich príkazov nastala výnimka končí blok try hľadá sa vhodný catch nenájde sa catch zavolá sa terminate() nekorektný koniec nájde sa catch vykonajú sa príkazy v catch pokračuje sa ďalej

14 Príkaz throw vo funkcii
Už sme si hovorili, že príkaz throw môže byť volaný nielen priamo v bloku try, ale aj vo funkcii, ktorá je z tohto bloku volaná. V tomto prípade plní throw v podstate úlohu príkazu return – spôsobí korektný návrat z funkcie, teda návrat so zrušením lokálnych premenných. Príklad: Operátor / pre komplexné čísla Do triedy KomplexnéČíslo doplníme ďalší operátor. V ňom sa bude nachádzať aj príkaz throw. KomplexneCislo KomplexneCislo::operator /(KomplexneCislo x) { KomplexneCislo z; if (x.a==0 && x.b==0) throw Delenie_nulou; z.ZmenA((a*x.a+b*x.b)/(x.a*x.a+x.b*x.b)); z.ZmenB((b*x.a-a*x.b)/(x.a*x.a+x.b*x.b)); return z; }

15 Príkaz throw vo funkcii
V hlavnom programe potom budeme mať v bloku try len samotné delenie: KomplexneCislo x(1,2), y(3,-1), z; cout<<"Komplexne cisla: "; x.Vypis(); cout<<", "; y.Vypis(); cout<<endl; try { z=x/y; } catch(int vynimka) if (vynimka==Delenie_nulou) cout<<"Nulou delit neviem!"<<endl; return 1; cout<<"Program prebehol bez problemov"<<endl; cout<<"Podiel tychto cisel: "; z.Vypis();

16 Príkaz throw vo funkcii
Na výstupe môžeme sledovať, ako vznikajú a zanikajú objekty. Všimnime si aj rušenie kópie potrebnej pri vrátení komplexného čísla funkciou /. Ak delíme nulou, kópia pri vrátení sa nevytvorí, ale vidíme, že lokálna premenná z bola korektne zrušená vďaka throw.

17 Výnimky viacerých typov
Príklad: Vytváranie 3D obrázku z 2D rezov Úlohou programu bude zo zadaného zoznamu 2D obrázkov vytvoriť pospájaním jeden 3D obrázok. Vstupné obrázky budú mať vždy rovnaký názov a líšiť sa budú len v indexe, teda napr obr1.raw, obr2.raw, obr3.raw atď. Takisto budú mať rovnaké rozmery. Vytvoríme triedu SpracovávačObrázkov, ktorá bude mať metódu Vytvor3Dz2D. Táto metóda oznámi výnimku v prípade, ak nastane problém so vstupným alebo výstupným súborom. V prvom prípade vráti throw meno neplatného vstupného súboru, v druhom len hodnotu typu int. class SpracovavacObrazkov { private: fstream vstup, vystup; public: void Vytvor3Dz2D(char m_vs[100], char m_vy[100], int zaciatok, int koniec, int x, int y); };

18 Výnimky viacerých typov
void SpracovavacObrazkov::Vytvor3Dz2D(char m_vs[100], char m_vy[100], int zaciatok, int koniec, int x, int y) { char meno_vstupu[100]; vystup.open(m_vy,fstream::out | fstream::binary); if (!vystup.is_open()) throw(ChybaVystupu); for (int i=zaciatok; i<=koniec; i++) sprintf(meno_vstupu,"%s%i.raw",m_vs,i); vstup.open(meno_vstupu,fstream::in | fstream::binary); if (!vstup.is_open()) throw(meno_vstupu); for (int j=0;j<x;j++) for (int k=0;k<y;k++) vystup.put(vstup.get()); vstup.close(); } vystup.close(); Konštanta ChybaVýstupu je definovaná na začiatku programu: #define ChybaVystupu 1

19 Výnimky viacerých typov
V hlavnom programe potom máme príkaz catch pre oba typy výnimiek: SpracovavacObrazkov so; try { so.Vytvor3Dz2D("rez","vystup.raw",0,61,128,128); } catch (int vynimka) if (vynimka==ChybaVystupu) cout<<"Do zadaneho suboru neviem zapisovat!"<<endl; return 1; catch (char *vynimka) cout<<"Subor "<<vynimka<<" neexistuje alebo je poskodeny"<<endl; return 2; cout<<"Teraz si mozte 3D obrazok pozriet"<<endl;

20 Zachytenie všetkých výnimiek
Ak chceme mať príkaz catch, ktorý by zachytil výnimku ľubovoľného typu, môžeme to spraviť takto: catch(...) {príkazy} Napr. v predchádzajúcom príklade by sme mohli hlavný program prepísať: SpracovavacObrazkov so; try { so.Vytvor3Dz2D("rez","vystup.raw",0,61,128,128); } catch(...) cout<<"Stalo sa nieco nedobre!"<<endl; return 1; cout<<"Teraz si mozte 3D obrazok pozriet"<<endl;

21 Opätovné oznámenie výnimky
Niekedy môžeme chcieť, aby sa jedna výnimka spracovala na viacerých miestach. Napríklad, ak výnimka nastane vo funkcii, môže na ňu zareagovať samotná funkcia a potom ešte aj hlavný program, ktorý môže napríklad reagovať rovnakým spôsobom na výnimky z viacerých funkcií. Opätovné oznámenie výnimky dosiahneme tým, že v rámci catch zopakujeme použitie throw: catch(typ_výnimky výnimka) { ... throw; } Príklad: Spracovávač obrázkov s opätovným oznámením výnimky Prepíšeme predchádzajúci príklad tak, že chybová hláška sa bude vypisovať už vo funkcii Vytvor3Dz2D. Hlavný program potom zistí, či vo funkcii nastala nejaká výnimka (bez ohľadu na typ) a ak áno, oznámi to užívateľovi.

22 Opätovné oznámenie výnimky
funkcia Vytvor3Dz2D bude teraz vyzerať takto: void SpracovavacObrazkov::Vytvor3Dz2D(char m_vs[100], char m_vy[100], int zaciatok, int koniec, int x, int y) { char meno_vstupu[100]; try vystup.open(m_vy,fstream::out | fstream::binary); if (!vystup.is_open()) throw(ChybaVystupu); } catch (int vynimka) if (vynimka==ChybaVystupu) cout<<"Do zadaneho suboru neviem zapisovat!"<<endl; throw; ...

23 Opätovné oznámenie výnimky
for (int i=zaciatok; i<=koniec; i++) { try sprintf(meno_vstupu,"%s%i.raw",m_vs,i); vstup.open(meno_vstupu,fstream::in | fstream::binary); if (!vstup.is_open()) throw(meno_vstupu); for (int j=0;j<x;j++) for (int k=0;k<y;k++) vystup.put(vstup.get()); vstup.close(); } catch (char *vynimka) cout<<"Subor "<<vynimka<<" neexistuje alebo je poskodeny"<<endl; throw; vystup.close();

24 Opätovné oznámenie výnimky
Hlavný program bude zachytávať výnimky všetkých typov: int main() { SpracovavacObrazkov so; try so.Vytvor3Dz2D("rez","vystup.raw",0,61,128,128); } catch(...) cout<<"3D obrazok si asi nepozrieme :-("<<endl; return 1; cout<<"Teraz si mozte 3D obrazok pozriet"<<endl; return 0;

25 Alokácia a výnimky Jedna z najčastejších výnimiek vzniká pri neúspešnej alokácii pamäte, najmä ak chceme alokovať viac miesta, ako máme k dispozícii. Príklad: Alokácia bloku pamäte podľa požiadavky užívateľa int velkost; char *blok; cout<<"Kolko bytov pamate mam alokovat? "; cin>>velkost; try { blok=new char[velkost]; if (!blok) throw ChybaAlokacie; //pri neúspechu je hodnota smerníka 0 } catch(int vynimka) if (vynimka==ChybaAlokacie) cout<<"Tak toto nie.."<<endl; return 1; cout<<"V poriadku, alokovane!"<<endl; delete[] blok;

26 Alokácia a výnimky Otestujeme program v rôznych prípadoch.
Ak je alokácia úspešná: Ak sa pokúšame alokovať príliš veľa miesta:

27 Alokácia a výnimky Ak zadáme nevhodný rozmer, napr. zaporné číslo, vznikne v tomto prípade iný typ výnimky: Ak chceme ošetriť aj takýto prípad, musíme to urobiť osobitne, napr.: try { if (velkost<0) throw ChybaAlokacie; blok=new char[velkost]; if (!blok) throw ChybaAlokacie; }

28 Alokácia a výnimky V niektorých verziách C++ je operátor new navrhnutý tak, aby oznamoval výnimku, ak je alokácia pamäte neúspešná. V bloku try už preto nemusíme mať príkaz throw, zahŕňa ho samotné new. Výnimka, ktorú new oznamuje, je typu bad_alloc. Príklad: Alokácia bloku pamäte v Linuxe try { blok=new char[velkost]; } catch(bad_alloc vynimka) cout<<"Tak na to zabudnite!"<<endl; return 1;

29 Alokácia a výnimky Znova otestujeme program pre rôzne prípady.
Ak je alokácia v poriadku: Ak chceme alokovať príliš veľa miesta: V tomto prípade sa spracuje a prípad, keď zadáme záporný rozmer:

30 Výnimky v konštruktoroch
Príklad: Výpočet funkčných hodnôt Vytvoríme triedu Graf, ktorá pripraví údaje potrebné na vykreslenie grafu funkcie – pre zadané hodnoty x vypočíta a uloží hodnoty f(x). Trieda bude obsahovať dve dynamicky alokované polia – pre hodnoty x a f(x). class Graf { private: float *x, *fx, a, b; long int n; public: Graf(long int n, float a, float b); ~Graf(); void VypocitajX() {for (int i=0;i<n;i++) x[i]=i*(b-a)/(n-1);} void VypocitajOdmocninu() {for (int i=0;i<n;i++) fx[i]=sqrt(x[i]);} void Vypis(); void ZmenX(int i,float v) {x[i]=v;} };

31 Výnimky v konštruktoroch
Konštruktor triedy Graf oznamuje výnimky pri alokácii: Graf::Graf(long int n, float a, float b) { this->n=n; this->a=a; this->b=b; cout<<"Alokujem pole x.."<<endl; x=new float[n]; if (!x) throw ChybaAlokacie; cout<<"Podarilo sa :-)"<<endl; cout<<"Alokujem pole fx.."<<endl; fx=new float[n]; if (!fx) throw ChybaAlokacie; cout<<"Aj to sa podarilo :-)"<<endl; }

32 Výnimky v konštruktoroch
Deštruktor a metóda Výpis: Graf::~Graf() { cout<<"Dealokujem x.."<<endl; delete[] x; cout<<"Dealokujem fx.."<<endl; delete[] fx; } void Graf::Vypis() cout<<"Vypis funkcnych hodnot:"<<endl; for (int i=0;i<n;i++) cout<<"x: "<<x[i]<<", fx: "<<fx[i]<<endl;

33 Výnimky v konštruktoroch
float a,b; long int n; cout<<"Zaciatok intervalu: "; cin>>a; cout<<"Koniec intervalu: "; cin>>b; cout<<"Kolko hodnot sa ma vypocitat? "; cin>>n; try { Graf g(n,a,b); g.VypocitajX(); g.VypocitajOdmocninu(); g.Vypis(); } catch(int vynimka) { if (vynimka==ChybaAlokacie) cout<<"Tuto alokaciu asi nezvladnem :-("; return 1; cout<<"Program prebehol bez problemov!"<<endl;

34 Výnimky v konštruktoroch
Ak všetko prebehne v poriadku, môže nám dať program napríklad takýto výstup:

35 Výnimky v konštruktoroch
V prípade, že zadáme príliš veľký rozmer, nastane výnimka: Všimnime si nasledujúci prípad. V tomto prípade sme zadali taký rozmer, že sa síce podarilo alokovať miesto pre x, ale pre fx sa to už nepodarilo. Konštruktor sa teda nedokončí, vytvorenie objektu triedy Graf neprebehne. To znamená, že sa nezavolá ani deštruktor (vidíme to aj z výstupu). Miesto pre x sa teda nemá kde uvoľniť, pritom je už alokované. Vznikol memory leak. Takýmto situáciám by sme mali predchádzať.

36 Výnimky v konštruktoroch
V našom prípade môžeme situáciu vyriešiť napr. tak, že príslušný catch bude už v konštruktore: Graf::Graf(long int n, float a, float b) { this->n=n; this->a=a; this->b=b; try cout<<"Alokujem pole x.."<<endl; x=new float[n]; if (!x) throw ChybaAlokacie; } catch(int vynimka) if (vynimka==ChybaAlokacie) cout<<"Pole x neviem alokovat :-("<<endl; throw;

37 Výnimky v konštruktoroch
V ďalšej časti budeme mať catch pre alokáciu fx, pričom ak nastane výnimka, dealokuje sa aj miesto alokované pre x: cout<<"Podarilo sa :-)"<<endl; try { cout<<"Alokujem pole fx.."<<endl; fx=new float[n]; if (!fx) throw ChybaAlokacie; } catch(int vynimka) if (vynimka==ChybaAlokacie) cout<<"Pole fx je uz na mna privela :-("<<endl; cout<<"Dealokujem aj x"<<endl; delete[] x; throw; cout<<"Aj to sa podarilo :-)"<<endl;

38 Výnimky v konštruktoroch
Na výstupe vidíme, že ak nastane situácia, že x sa podarí alokovať a fx nie, program sa korektne ukončí:

39 Zhrnutie Výnimka je problém, ktorý nastane počas behu programu a zabráni jeho správnemu priebehu. Výnimky sú spôsobené vonkajšími okolnosťami, nie programátorom. Programátor ich ale môže predvídať a dopredu ošetriť výnimočné situácie. Na zachytenie a spracovanie výnimiek máme v C++ trojicu príkazov try, throw a catch. V bloku try je obsiahnutá tá časť kódu, v ktorej môže nastať výnimka. Príkaz throw musí byť buď v bloku try alebo vo funkcii, ktorá je z tohto bloku volaná. Každý typ výnimky musí mať zodpovedajúci príkaz catch.


Download ppt "Obsluha výnimiek."

Similar presentations


Ads by Google