w25 obsluga sytuacji wyjatkowych


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