230 likes | 359 Views
Wyjątki. Po co nam wyjątki?. Często pisząc jakiś kod staramy się go uczynić uniwersalnym, jesteśmy w stanie wykryć sytuacje niepoprawne, ale nasz kod może być używany na różny sposób, nie jesteśmy w stanie przewidzieć jak zareagować na błąd.
E N D
Po co nam wyjątki? • Często pisząc jakiś kod staramy się go uczynić uniwersalnym, jesteśmy w stanie wykryć sytuacje niepoprawne, ale nasz kod może być używany na różny sposób, nie jesteśmy w stanie przewidzieć jak zareagować na błąd. • Ze strony kogoś korzystającego z danej biblioteki sytuacja odwrotna – wiadomo jak obsłużyć, ale nie wiadomo jak wykryć.
Wyjątki w C++ • W C++ wprowadzono mechanizm wyjątków, jako sposób na zgłaszanie błędów w momencie ich wykrycia i na definiowanie zachowania programu w przypadku pojawienia się błędów. Obsługa wyjątków realizowana jest w C++ z użyciem słów kluczowych catch throw i try. • throw — zgłoszenie wyjątku • try — poprzedza blok instrukcji (obowiązkowo w nawiasach klamrowych) w którym można spodziewać się wystąpienia wyjątków • catch — definiuje reakcję na wystąpienie wyjątku (też obowiązują nawiasy).
throw, try i catch double funkcja()// funkcja która o niepowodzeniu // swoich obliczeńinformuje // zgłaszając wyjątek { // pewne obliczenia // w razie wykrycia błędu throw 1; // zgłoszenie wyjątku // w przeciwnym przypadku dalsze obliczenia // wykryto inny błąd throw 2; // zgłoszenie innego wyjątku // ... }
void przykl0() { try// wykonujemy operacje, przewidujemy // możliwość zgłoszenia przez nie wyjątku {// klamra obowiązkowa ! funkcja(); cout << „bez błędów :-) "; } catch (int i) // w przypadku zgłoszenia wyjątku // w bloku po try sterowanie zostanie // przekazane do bloku instrukcji po catch // (do tzw.: procedury obsługi wyjątku) {// klamra obowiązkowa ! cout << "wykryto błąd " << i; } // jeżeli funkcja funkcja() zakończy się poprawnie // (bez zgłaszania wyjątku), to procedura obsługi wyjątku zostanie // pominięta i rozpocznie się wykonywanie dalszych instrukcji // dalsze instrukcje cout << „koniec przykladu 0 "; }
throw, try i catch • throw – jest operatorem, po nim następuje wyrażenie które określi rodzaj wyjątku, mogą być różne typy/klasy wyjątków; • catch – może pojawić się tylko po try lub po innym catch, w nawiasie określamy typ wyjątku i zmienną/obiekt która przekazuje informacje o wyjątku. • Jeżeli do obsługo błędu wystarczy sam fakt jego wykrycia to podajemy typ nie podając zmiennej/obiektu. • Blok po catch nazywamy procedurą obsługi wyjątku. • Procedura obsługi wyjątku może znajdować się w tej samej funkcji w której następuje zgłoszenie wyjątku.
void przykl1() { try { // obliczenia i niepowodzenie throw ‘a’; // dalsze obliczenia i dalsze niepowodzenia throw 1; // ... } catch (int i) { cout << "wykryto błąd numer " << i; } catch (char) { cout << "wykryto błąd bez numeru"; } }
Obsługa zgłoszenia wyjątku • W momencie wykrycia wyjątku przeglądany jest stos od funkcji w której zgłoszono wyjątek przez łańcuch wywołań funkcji aż do main(). • Przy wychodzeniu „w górę” wykonywane są destrukcje obiektów lokalnych (tak jak przy normalnym opuszczeniu funkcji). • Jeżeli nie napotka się nigdzie procedury obsługi zgłoszonego wyjątku (może być ich więcej niż jedna) to nastąpi przerwanie programu. • Jeżeli jest kilka na różnych poziomach zagnieżdzenia (wywołań funkcji) to wykonana zostanie najwcześniej znaleziona, czyli ta którą sterowanie minęło jako ostatnią.
Obsługa zgłoszenia wyjątku void przykl2() { try { przykl0(); } catch (int i) { cout << "wykryto błąd numer " << i; // ten wyjątek zgłaszany przez funkcja() // jest obsługiwany już w przykl0(), // więc ta procedura obsługi wyjątku nie zostanie wykonana } }
Ponowne zgłoszenie wyjątku • Wyjątek jest uznawany obsłużony w momencie rozpoczęcia wykonywania procedury obsługi wyjątku — następujący kod nie powoduje zapętlenia: try { // pwoduje wystąpienie wyjątku klasy xxx } catch (xxx) {// nieudana próba naprawy throw xxx; // pójdzie „w górę” do kodu który wywołał ten } • Operator throw bez argumentu w procedurze obsługi wyjątku powoduje ponowne jego zgłoszenie. • Tutaj zamiast „throw xxx;” wystarczyłoby napisać „throw;”.
Zagnieżdzanie procedur obsługi try { // kod, który może zgłosić wyjątek klasy zzz } catch (zzz) { try // próbujemy coś zrobić z wyjątkiem { // próba może się nie powieść, kod naprawy // też może zgłosić wyjątek klasy zzz throw; // znaczy to samo co „throw zzz;”, } catch (zzz) {// obsługa wyjątku zgłoszonego podczas obsługi wyjątku } }
Klasy wyjątków • Można zdefiniować wiele klas wyjątków, albo do opisu wyjątku wykorzystać istniejące już typy/klasy. • W bibliotekach klas korzystających z wyjątków zazwyczaj korzysta się ze specjalnych klas do określania rodzaju wyjątków. • Stosowanie klas jako typów wyjątków jest wygodne, możemy tworzyć klasy o nazwach opisujących typy wyjątków,
Klasy wyjątków • Przykład: klasa błędu: dielenie przez zero class błąd_dzielenia_przez_0 {}; • w razie potrzeby zgłoszenia wyjątku korzystamy z konstruktora throw błąd_dzielenia_przez_0(); • i wyłapujemy wyjątek: catch (błąd_dzielenia_przez_0) { /* ... */ } • W powyższym przykładzie skorzystaliśmy z klasy (a nie z obiektu, przez konstruktory parametrowe możemy również przekazywać obiekty o istotnej wartości)
Dziedziczenie klas wyjątków • Przy obsłudze klas wyjątków możemy również skorzystać z dziedziczenia – procedura obsługi wyjątku klasy bazowej obsłuży wyjątek klasy pochodnej (niejawna konwersja automatyczna – ale uwaga: tylko dziedziczącej klasę bazową publicznie). class blad_matematyczny {}; class nadmiar:public blad_matematyczny {}; class niedomiar:public blad_matematyczny {}; class dzielenie_przez_0:public blad_matematyczny {};
Dziedziczenie klas wyjątków • Jeżeli jest kilka procedur obsługi wyjątku mogących obsłużyć dany wyjątek, to zostanie wykonana pierwsza (najbliższa bloku try) z nich. try { throw nadmiar(); } catch(nadmiar) // obsługa nadmiaru i wyjątków klas {// pochodnych od nadmiar cout << "\nnadmiar"; } catch(blad_matematyczny) // obsługa błędów matematycznych i jego {// pochodnych , za wyjątkiem już obsłużonego cout << "\nbm"; // nadmiaru i wyjątków } // klas pochodnych od nadmiar
Dziedziczenie klas wyjątków • Gdyby obsługa błędu_matematycznego poprzedzała obsługę nadmiaru to obsługiwała by zawsze również nadmiar: try { throw nadmiar(); } catch(blad_matematyczny) // obsługa błędów matematycznych {// i pochodnych klasy blad_matematyczny cout << "\nbm"; } catch(nadmiar) // obsługa nadmiaru i wyjątków klas pochodnych od nadmiar // nie zostanie nigdy wykorzystana bo te wyjątki obsługuje { // procedura obsługi wyjątku blad_matematyczny cout << "\nnadmiar"; }
Obsługa nieobsłużonych wyjątków • Za pomocą instrukcji catch(...) tworzymy procedurę obsługi wszystkich klas wyjątków void m() { try { // coś } catch(...) { // zrób porządki po niedokończonej funkcji throw; // powtórnie zgłoś wyjątek jako sygnał niepowodzenia } }
Specyfikowanie jawne wyjątków zgłaszanych przez metody void C::met() throw(int) { // coś throw(12); // coś } • To nie jest obowiązkowe • Ale przyda się programiscie używającemu met()
Specyfikowanie jawne wyjątków zgłaszanych przez metody void C::met() throw() { // coś // throw zabronione tutaj! // coś }
Obsługa niewyspecyfikowanych wyjątków set_unexpected (my_unexpected) ustawia handler niewyspecyfikowanych wyjątków domyślny handler (void unexpected()) wywołuje terminate() handler będzie użyty jeżeli wyrzucony wyjątek nie zgadza się ze specyfikacją wyjątków rzucającej funkcji jeżeli rzucająca funkcja specyfikuje, że może wyrzucić bad_exception, to powtórne wyrzucenie wyjątku przez handler, albo wyrzucenie innego niewyspecyfikowanego wyjątku, spowoduje automatyczne wyrzucenie wyjątku bad_exception
Wyjątki w bibliotece standardowej Składniki biblioteki standardowej wyrzucają wyjątki klas dziedziczących po: class exception // std::exception { public: exception () throw(); exception (const exception&) throw(); exception& operator= (const exception&) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); // zwróć opis // null-terminated, treść zależna od implementacji biblioteki }
Wyjątki w bibliotece standardowej Składniki biblioteki standardowej wyrzucają wyjątki (std::) : bad_alloc wyrzuca new w przypadku niepowodzenia alokacji bad_cast rzuca dynamic_cast gdy nie powiedzie się konwersja referencji bad_exception gdy typ wyjątku nie pasuje do żadnego catch, lub do typów wyspecyfikowanych w nagłówku funkcji rzucającej (po wywołaniu unexpected()) bad_typeid rzuca typeid np. gdy otrzyma jako argument NULL
Wyjątki w bibliotece standardowej Składniki biblioteki standardowej wyrzucają wyjątki (std::) : runtime_error klasa bazowa dla klas definiujących wyjątki czasu wykonania programu (range_error, overflow_error, underflow_error) logic_error dla błędów wewnętrznej logiki programu; klasa bazowa dla innych (domain_error, invalid_argument, length_error, out_of_range) ios_base::failure błędy zgłaszane przez bibliotekę iostream …