Obsługa sytuacji wyjątkowych
" jakość kodu cel ka\dego programisty wymaga:
o starannego testowania po to by eliminować
problemy, zanim pojawią się one w trakcie
normalnego działania
o pisania kodu tak, by był on odporny na nieprzewi-
dziane sytuacje powstałe w trakcie jego wykonania
" problem obsługi błędów w C było to mo\liwe (np.
sprawdzanie, czy plik poprawnie otwarty, czy alokacja
pamięci wykonała się właściwi), ale często ignorowane
przez programistów
o przykład funkcja printf zwraca wynik ilość
wypisanych znaków, lub liczbę ujemną gdy błąd;
bardzo rzadko u\ywana przez programistów do
diagnostyki błędów
o to ilustruje powód zaniedbywania kontroli błędów w
C: jego wykonanie jest \mudne i prowadzi do
rozdęcia kodu
o wymagane jest ciasne związane kodu diagnostyki
błędu z wywołaniem funkcji, która mo\e błąd
wyprodukować
o rezultat: mieszanie kodu diagnostyki błędu z kodem
logiki programu, całość niezgrabna i kłopotliwa w
u\yciu
" C++ znacznie poprawiona i uproszczona obsługa
sytuacji wyjątkowych; główne punkty to:
o kod obsługi błędu nie miesza się z kodem, który
działa, gdy błędu nie ma
o błąd nie mo\e być zignorowany: albo sytuacja
wyjątkowa zostanie załatwiona przez odpowiedni
fragment kodu, albo nastąpi przerwanie wykonania
programu
o mo\liwe rozdzielenie miejsca powstania sytuacji
wyjątkowej od miejsca, w którym następuje reakcja
na nią (wa\ne, gdy podjęcie decyzji o sposobie
reakcji wymaga znajomości szerszego kontekstu ni\
ten, który jest dostępny w miejscu powstania błędu)
" typowy sposób postępowania w sytuacjach wyjątkowych
utworzenie obiektu wyjątku i przekazanie go do
(nieznanego jeszcze) miejsca, gdzie właściwa reakcja
będzie mogła zostać uruchomiona
" ró\ne mo\liwe sytuacje wyjątkowe powodują
wytworzenie i przekazanie ró\nych typów obiektów
wyjątku
" oprócz typu obiektu informacja o sytuacji wyjątkowej
mo\liwa do odczytania z wartości obiektu wyjątku
Wyrzucanie wyjątku
" gdy powstanie sytuacja wyjątkowa, która wymaga
odwołania się do szerszego kontekstu odsyłamy
informację do obszaru gdzie ten szerszy kontekst będzie
znany
" przekazanie informacji poprzez utworzenie oraz
wyrzucenie (katapultowanie) obiektu wyjątku (specjalna
instrukcja throw)
Aapanie wyjątku
" wyrzucony obiekt wyjątku mo\e zostać przechwycony w
odpowiednich miejscach, gdzie dysponujemy większą
wiedzą (szerszy kontekst) potrzebną do podjęcia decyzji,
co w danej sytuacji zrobić
" fragmenty kodu, które wykonują reakcje na sytuacje
wyjątkowe procedury obsługi sytuacji wyjątkowych
(exception handlers)
Mechanizm obsługi sytuacji wyjątkowych w C++
" obsługa sytuacji wyjątkowych w C++ działa w oparciu o
mechanizm składający się z:
o określenia obszaru, w którym spodziewamy się
mo\liwości wystąpienia sytuacji wyjątkowych blok
try
o w razie potrzeby sygnalizacji wystąpienia sytuacji
wyjątkowej połączone z przekazanie informacji o jej
szczegółach instrukcja throw
o przygotowania fragmentów kodu, które zajmą się
wychwyceniem obiektu wyjątku i reakcją na wykrytą
sytuację wyjątkową (bloki catch występujące tu\ pod
blokiem try, zawierające definicję exception
handlers)
" schemat fragmentu kodu realizującego powy\szy
mechanizm:
// program części bez ryzyka
&
// początek części ryzykownej
try {
// treść części ryzykownej
// w razie potrzeby instrukcja throw
}
catch (type_1 id1) {
// załatwia wyjątek typu type_1
// mo\liwe wykorzystanie wartości obiektu id1
}
catch (type_2) {
// załatwia wyjątek typu type_2
// wartość obiektu wyjątku niewykorzystana
}
&
catch (type_N idN) {
// załatwia wyjątek typu type_N
}
// kod, który wykonuje się gdy nie ma sytuacji wyjątkowych
Działanie mechanizm blok try, instrukcja throw
" fragment kodu, w którym spodziewamy się mo\liwości
wystąpienia sytuacji wyjątkowych, które chcemy załatwić
przy pomocy omawianego schematu, musi być zamknięty
w bloku try
" gdy sytuacja wyjątkowa sygnalizacja poprzez rzucenie
wyjątku :
throw nazwa_obiektu;
" wtedy sterowanie przekazane do odpowiedniego
exception handlera
" wyjątek mo\e być te\ rzucony z wnętrza funkcji, która jest
(bezpośrednio lub pośrednio) wywołana w bloku try ;
wtedy:
o funkcja ulega przerwaniu, nie zwraca wartości przez
return, tylko wyrzucany obiekt wyjątku (mimo, \e
jest to obiekt innego typu ni\ typ funkcji)
o sterowanie przechodzi do właściwego exception
handlera (a nie miejsca wywołania funkcji)
" wszystkie obiekty lokalne utworzone do chwili wyrzucenia
wyjątku zostaną zdekomponowane (następuje wywołanie
właściwych destruktorów) stack unwinding
" informacje o typie wyjątku niesione przez:
o typ obiektu wyjątku
o wartość tego obiektu
" gdyby wyjątek był rzucony poza blokiem try przerwanie
działania programu
Działanie mechanizm blok catch
" wyrzucany wyjątek musi zostać załatwiony przez właściwy
exception handler właściwy blok catch
" składnia tego bloku:
catch (nazwa_typu nazwa_obiektu) {
// instrukcje bloku
}
" na ogół: po bloku try kilka bloków catch
" po spowodowanym wyjątkiem wyjściu z bloku try
przeglądnięcie najbli\szych bloków catch w poszukiwaniu
takiego, którego typ w nagłówku jest zgodny z typem
rzuconego wyjątku
" dopasowanie typu wyjątku i typu nagłówka bloku catch nie
musi być idealne rządzą tym następujące zasady:
o obiekt/referencja klasy pochodnej mo\e być
dopasowany do bloku catch, który spodziewa się
obiektu/referencji klasy podstawowej
o gdy blok catch spodziewa się obiektu (nie referencji)
klasy podstawowej, to dopasowaniu obiektu wyjątku
klsy pochodnej towarzyszy jego obcięcie (object
slicing)
o w procesie dopasowania nie są stosowane
automatyczne konwersje, które mogłyby zmienić
jeden typ w drugi
Przykład 1: dopasowanie klasa podstawowa/klasa pochodna
#include
class X {
public:
class Klopot { };
class Maly : public Klopot {};
class Duzy : public Klopot {};
void f() { throw Duzy( );}
};
int main( )
{
X x;
try {
x.f( );
}
catch (X::Klopot &) {
cout << "Zlapany wyjatek typu Klopot\n";
}
catch (X::Maly &) {
cout << "Zlapany wyjatek typu Maly Klopot\n";
}
catch (X::Duzy &) {
cout << "Zlapany wyjatek typu Duzy Klopot\n";
}
cout << "Normalne dzialanie\n";
}
___________________________________
Zlapany wyjatek typu Klopot
Normalne dzialanie
o wybrany zostanie pierwszy handler, który da się
dopasować (niekoniecznie najlepiej dopasowany)
o istotna kolejność bloków catch w przykładzie powy\ej
handlery typu X::Duzy i X::Maly nigdy nie zostaną u\yte
Przykład 2: dopasowanie a konwersje standardowe
#include
class w1 { };
class w2 {
public:
w2(const w1 &w) { }
};
void f()
{
throw w1();
}
int main()
{
try {
f();
}
catch (w2 &) {
cout << "Zlapany wyjatek typu w2\n";
}
catch (w1 &) {
cout << "Zlapany wyjatek typu w1\n";
}
cout << "Normalne dzialanie\n";
}
___________________________________
Zlapany wyjatek typu w1
Normalne dzialanie
o zdefiniowana jest konwersja w1 w2
o handler typu w2 poprzedza handler typu w1
o rzucamy wyjątek typu w1
o handler typu w2 (który mógłby być dopasowany, gdyby
mo\na było u\yć konwersji w1 w2) nie jest wywołany
Kolejność bloków catch
" działanie mechanizmu obsługi wyjątków przy wyborze
właściwego bloku catch analogiczne do działania
konstrukcji wielokrotnego else if
" o tym, który blok catch zostanie dopasowany mo\e
decydować kolejność ich umieszczenia w sekwencji
" kolejność bloków catch mo\e być istotna w sytuacji, gdy
typy występujące w nagłówkach bloków jest powiązana
relacją dziedziczenia
" niekiedy handler, który wyłapuje wszystkie wyjątki:
catch (& ) {
// instrukcje bloku
}
" blok catch (& ) powinien wystąpić na ostatniej pozycji
sekwencji inaczej blokuje wszystkie handlery
występujące ni\ej
" wystąpienie bloku catch (& ) na pozycji innej ni\ ostatnia
kompilator traktuje jako błąd kompilacji
" blok catch (& )
o nie jest w stanie ustalić typu ani wartości obiektu
wyjątku
o zwykle dokonuje pewnych czynności standardowych,
takich jak:
wypisanie ostrze\enia o błędzie
zwolnienia pewnych zasobów
o często z wnętrza bloku catch (& ) następuje
odrzucenie obiektu wyjątku przekazanie go do
ponownego rozpatrzenia przez sekwencję exception
handlers na wy\szym poziomie zagnie\d\enia
o składnia instrukcji odrzucenia wyjątku:
throw; // bez agrumentu
Zagnie\d\enie bloków try, catch
" bloki try, catch stanowią pewną całość
" konstrukcja try catch mo\e być zagnie\d\ona:
o przykład : w bloku try mamy wywołanie funkcji, która
sama w sobie ma swój blok try i odpowiadające
bloki catch
Przykład schematu zagnie\d\enia:
try {
&
try {
&
throw aaa;
&
} // koniec wewnętrznego try
catch ( int ) {
&
}
catch ( float x ) {
&
}
// etc.
} // koniec zewnętrznego try
catch ( xxx ) {
&
}
catch ( zzz ) {
&
}
// itd.
// dalsze instrukcje
" zasady działania mechanizmu obsługi sytuacji
wyjątkowych w sytuacji zagnie\d\enia (zało\enie: zostaje
rzucony wyjątek w najbardziej zagnie\d\onym bloku)
o przeglądanie bloków catch skojarzonych z
wyjściowym blokiem try
o gdy uda się dopasować typ obiektu wyjątku do typu
nagłówka bloku wyjątek uwa\a się za załatwiony
o gdy nie udało się dopasować na danym poziomie,
kontynuujemy poszukiwania na płytszym poziomie
zagnie\d\enia (rekursywnie w górę)
o gdy na którymś poziomie hierarchii zagnie\d\enia
znajdziemy blok, który mo\e obsłu\yć wyjątek, to jest
on wykonywany (wyjątek jest uwa\any za obsłu\ony)
o po wykonaniu bloku catch, program zaczyna
wykonywać zwykłe instrukcje (le\ące poni\ej
ostatniego bloku catch) na tym poziomie
zagnie\d\enia, który potrafił obsłu\yć wyjątek
o gdy nie uda się znalezć właściwego bloku catch na
\adnym poziomie zagnie\d\enia, to wywołana
zostanie specjalna funkcja terminate() o
sygnaturze
void terminate( );
o funkcja ta ma w swoim ciele wywołanie funkcji
bibliotecznej abort() powoduje ona brutalne
przerwanie działania programu (bez zamknięcia
plików, czyszczenia buforów itp.)
o funkcja terminate() jest te\ wywoływana gdy:
w czasie obsługi wyjątku któryś z destruktorów
rzuca wyjątek
pomiędzy rzuceniem wyjątku a złapaniem go
konieczne jest u\ycie konstruktora kopiującego,
który rzuca ponowny wyjątek
" programista mo\e mieć wpływ na to, co się dzieje w czasie
przerwania programu przez terminate()
" osiąga się to przez:
o zdefiniowanie własnej funkcji o sygnaturze
void nazwa_funkcji();
która przejmie rolę terminate( )
o wskazanie kompilatorowi, funkcji która przejmie rolę
terminate() odbywa się to przez u\ycie funkcji
standardowej set_terminate():
( void (*wold)() ) set_terminate( void (*wnew)() )
gdzie: wnew wskaznik do funkcji, która ma zastąpić
terminate
wold wskaznik do funkcji, która do tej pory
pełniła rolę terminate
Przykład: zmiana funkcji terminate
#include
#include
using namespace std;
void zakoncz()
{
cout << "\tNienormalne zakonczenie -\n";
cout << "\tbrak handlera do rzucanego wyjatku\n";
system("Pause");
exit(100);
}
main()
{
try {
set_terminate(zakoncz);
//..
cout << "Rzucamy wyjatek, ktory nie moze byc zlapany ...\n";
throw 2.71;
}
catch (int k) {
cout << "Zlapany int = " << k << endl;
}
catch (char) {
cout << "Zlapany char\n";
}
cout << "Normalne zakonczenie\n";
}
__________________________________________
Rzucamy wyjatek, ktory nie moze byc zlapany ...
Nienormalne zakonczenie -
brak handlera do rzucanego wyjatku
Wyszczególnienie wyjątków
" gdy funkcja rzuca wyjątek, to powinniśmy być gotowi na
jego złapanie, czyli musimy zadbać o:
o umieszczenie wywołania funkcji w bloku try
o napisanie odpowiednich bloków catch
" gdy mamy dostęp do kodu zródłowego mo\emy łatwo
ustalić czy funkcja wyrzuca wyjątki i jakiego są one typu
" dla funkcji bibliotecznych mamy tylko nagłówek
(deklaracja) nie mamy treści ciała
" aby dać szansę programiście na właściwą reakcję na
wyjątki z funkcji bibliotecznych stosujemy tzw.
wyszczególnienie wyjątków dopisek do deklaracji funkcji
określający wykaz typów wyjątków, jakich mo\na się
spodziewać z wnętrza funkcji
Przykłady:
void f1() throw(int, float);
f1 mo\e rzucać wyjątki typu int albo typu float
int f2(float z) throw(X, Y); // X i Y nazwy klas
f2 mo\e rzucać wyjątki typu X lub publicznej klasy
pochodnej od X albo typu Y lub publicznej klasy pochodnej
od Y
void f3();
zapis nie mówi nic o typie wyjątków; zakładamy, \e f3 mo\e
rzucać wyjątki dowolnego typu
char* f4() throw( );
zapis oznacza, \e funkcja f4 nie rzuca \adnych wyjątków
" wyszczególnienie wyjątków tylko obietnica co do typu
wyjątków; mo\e się zdarzyć, \e wystąpi wyjątek w nim nie
wymieniony
" gdy taka sytuacja wystąpi (funkcja rzuca niewyszczegól-
niony wyjątek na który nie byliśmy przygotowani) to
wywołana zostanie specjalna funkcja:
void unexpected( );
" funkcja unexpected() jako ostatnią czynność wywołuje
funkcję terminate()
" programista mo\e zmienić sposób zakończenia
unexpected() (z wywołania terminate( ) na
wywołanie innej, dostarczonej przez siebie funkcji)
poprzez:
o napisanie własnej funkcji o sygnaturze
terminate(), np.:
void moje_niespodziwane_zakonczenie( );
o wskazanie kompilatorowi funkcji, która zastąpi
terninate() odbywa się to przez u\ycie standardowej
funkcji set_unexpected():
( void (*wold)() ) set_unexpected( void (*wnew)() )
gdzie: wnew wskaznik do funkcji, która ma zastąpić
terminate
wold wskaznik do funkcji, która do tej pory
pełniła rolę terminate
o podmieniona funkcja zastępująca terminate()
zwykle
pomaga wykryć, gdzie został rzucony
niespodziewany wyjątek
nie mo\e wrócić do wywołującej jej
unexpected() zatem rzuca nowy wyjątek
(taki, którego się spodziewaliśmy), albo
wywołać terminate(), albo
zakończyć program przez abort lub exit
Wyszukiwarka
Podobne podstrony:
Nowy Jork Przygotowany Co robić w razie sytuacji wyjątkowych
2 11 Obsługa wyjątków MFC (2)
5 Obsługa wyjątków (prezentacja)
Obsluga wyjatkow w grach w C
obsługa pojazdu Egzamin
DNS ObslugaNazw
Finanse Finanse zakładów ubezpieczeń Analiza sytuacji ekonom finansowa (50 str )
obsluga wiertarki stolowej
6 Sytuacja mniejszosci
Rozdział 04 System obsługi przerwań sprzętowych
Instrukcja obsługi bankomatu 1
więcej podobnych podstron