Wykład 9.
Transkrypt
Wykład 9.
OBSŁUGA WYJĄTKÓW Programowanie Obiektowe (język C++) Wykład 9. Tomasz Marks - Wydział MiNI PW -1- Tomasz Marks - Wydział MiNI PW -2- Przeciwdziałanie błędom wykonania programu (1) Przeciwdziałanie błędom wykonania programu (2) double& Vector :: operator [ ] ( int i ) { return V[ i ]; } // vector.h double& Vector :: operator [ ] ( int i ) { if ( i >= 0 && i < Size ) return V[ i ]; DisplayMessage( "komunikat o błędzie" ); exit(1); } // operacja niebezpieczna !!! // makro assert double& Vector :: operator [ ] ( int i ) { if ( i < 0 || i >= Size ) throw xcp_scope( i ); return V[ i ]; } // odrzucenie wyjątku // zewnętrzna (wzgl. Vector) klasa pomocnicza class Vector { int Size, Nr; double *V; static int DefSize, VCounter; void Init ( int = 0, double = 0, double * = 0 ); public: Vector ( ); Vector ( int size, double val = 0 ); Vector ( int size, double arr[ ] ); ………………………………. struct xcp_scope // wewnętrzna (w Vector) klasa pomocnicza { int index; xcp_scope ( int ); }; ………………………………. }; // fukcja systemowa lub własna // funkcja standardowa double& Vector :: operator [ ] ( int i ) { assert ( i >= 0 && i < Size ) return V[ i ]; } Tomasz Marks - Wydział MiNI PW struct xcp_memory { }; -3- Tomasz Marks - Wydział MiNI PW -4- Przeciwdziałanie błędom wykonania programu (3) Przeciwdziałanie błędom wykonania programu (4) // vector.cpp // metoda prywatna (wersja zmodyfikowana) void Vector :: Init ( int s, double val, double tab[ ] ) { if ( VCounter > 7 ) throw "Za duzo wektorow!"; Nr = ++VCounter; if ( s < 0 ) throw "Blad konstrukcji!"; if ( s <= 0 ) s = DefSize; try { V = new double [ s ]; } catch ( … ) { throw xcp_memory ( ); } if ( ! V ) throw xcp_memory ( ); Size = s; for ( s = 0; s < Size; ++s ) V [ s ] = tab ? tab [ s ] : val; } // implementacja konstruktora klasy xcp_scope Vector :: xcp_scope :: xcp_scope ( int i ) : index ( i ) { } // metoda prywatna (wersja oryginalna) void Vector :: Init ( int s, double val, double tab[ ] ) { Nr = ++VCounter; if ( s <= 0 ) s = DefSize; V = new double [ s ]; if ( ! V ) { Size = 0; return; } Size = s; for ( s = 0; s < Size; ++s ) V [ s ] = tab ? tab [ s ] : val; } Tomasz Marks - Wydział MiNI PW -5- Przeciwdziałanie błędom wykonania programu (5) Tomasz Marks - Wydział MiNI PW -6- Przeciwdziałanie błędom wykonania programu (6) #include "vector.h" Program wyświetli: void main ( ) { Vector w( 4 ); for ( int i = -1; i < 7; i++ ) try { cout << endl << "i = " << i << " "; Vector z( i ); w[ i ] = i; cout << "w[" << i << "] = " << w[ i ]; } catch ( const char * str ) { cout << str; } catch ( xcp_memory ) { cout << "Brak pamieci"; } catch ( Vector :: xcp_scope ex ) { cout << "Odrzucono indeks " << ex.index; } i i i i i i i i = = = = = = = = -1 0 1 2 3 4 5 6 Blad konstrukcji! w[ 0 ] = 0 w[ 1 ] = 1 w[ 2 ] = 2 w[ 3 ] = 3 Odrzucono indeks 4 Odrzucono indeks 5 Za duzo wektorow! } Tomasz Marks - Wydział MiNI PW -7- Tomasz Marks - Wydział MiNI PW -8- Obsługa wyjątków [1] 1. Obsługa wyjątków [2] Składnia instrukcji try-catch: try { 2. Bloki catch są nazywane procedurami obsługi wyjątków. // początek bloku try …............... …………… …………… 3. KaŜda procedura obsługi wyjątków ma określony typ sytuacji wyjątkowej, która ją aktywizuje. } catch ( ….. ) { ……… } catch ( ….. ) { ……… } .………………………….. catch ( ….. ) { ……… } // koniec bloku try // 1. blok catch // 2. blok catch 4. Zgłoszenie sytuacji wyjątkowej (throw) podczas wykonywania bloku try skutkuje przekazaniem pewnego obiektu do odpowiedniej procedury obsługi. // ostatni blok catch blok try wraz ze wszystkimi następującymi po nim blokami catch stanowi składniowo jedną całość, którą nazywamy instukcją try-catch Tomasz Marks - Wydział MiNI PW 5. Dopasowanie wyraŜenia throw do procedury obsługi polega na porównaniu typu wartości wyraŜenia throw z typami ( parametrów ) określonymi dla kolejnych procedur obsługi. -9- Tomasz Marks - Wydział MiNI PW Obsługa wyjątków [3] Obsługa wyjątków [4] 7. JeŜeli dopasowanie nie da pozytywnego rezultatu, t.zn. Ŝadna procedura catch nie zostanie uruchomiona, to dana instrukcja try-catch będzie uznana za zakończoną i nastąpi sprawdzenie, czy w aktualnie wykonywanej funkcji jest blok try zawierający daną instukcję try-catch. 6. Dopasowanie uznane będzie za pomyślne, jeŜeli spełniony zostanie jeden z trzech warunków: (i) oba typy są ściśle zgodne; (ii) typ określony dla parametru procedury obsługi jest publiczną klasą bazową klasy obiektu będącego wartością wyraŜenia throw; (iii) moŜliwe jest standardowe przekształcenie typu wartości wyraŜenia throw, będącej wskaźnikiem, na typ wskaźnikowy parametru procedury. JeŜeli taki blok istnieje, to wykonana zostanie próba dopasowania rozpatrywanygo wyjątku do występujących po nim procedur obsługi. JeŜeli taki blok nie istnieje, to wykonanie funkcji zostanie zakończone i odpowiedni blok będzie poszukiwany w funkcji wywołującej tę, w której został odrzucony wyjątek itd. itd. W skrajnym przypadku, jeŜeli dopasowanie nie zostanie zrealizowane w obrębie funkcji main(), obsługę wyjątku przejmie systemowa procedura obsługi wyjątków, która wyświetli odpowiedni komunikat i zakończy działanie programu. Po pozytywnym rozpoznaniu jednego z warunków (i), (ii), (iii), dalsze frazy catch nie będą analizowane. Tomasz Marks - Wydział MiNI PW -10- -11- Tomasz Marks - Wydział MiNI PW -12- Obsługa wyjątków [5] Obsługa wyjątków [6] 11. Deklaracja funkcji moŜe zawierać klauzulę, która stanowi ograniczenie zbioru sytuacji wyjątkowych zgłaszanych bezpośrednio lub pośrednio przez tę funkcję: 8. Zapis procedury obsługi nie musi zawierać nazwy przyjmowanego obiektu (parametru). Nazwa jest konieczna jedynie w sytuacji, gdy zachodzi rzeczywista potrzeba uŜycia wartości tego parametru. void fun1 ( …….. ); long fun2 ( …….. ) throw (const char*, xcp_scope&); // moŜe zgłaszać wyjątki wymienionych typów double fun3 ( ….. ) throw ( ); // nie moŜe zgłaszać Ŝadnych wyjątków Naruszenie zadeklarowanej specyfikacji spowoduje błąd wykonania programu. 9. Trzykropek … pozwala określić procedurę wychwytującą wyjątki wszystkich typów. 10. Wewnątrz procedury obsługi (catch) moŜna uŜyć frazy throw z pustym wyraŜeniem throw; Skutkuje to przekazaniem wyjątku do zewnętrznego bloku try. W innych kontekstach uŜycie pustego wyraŜenia throw jest błędem. Tomasz Marks - Wydział MiNI PW // moŜe zgłaszać dowolne wyjątki 12. Klauzula ograniczenia zbioru sytuacji wyjątkowych nie jest uwaŜana za element określenia typu funkcji ( tzn. nie wchodzi w skład deskryptora funkcji ). -13- Tomasz Marks - Wydział MiNI PW -14- Funktory (1) Definicja: Funktorem (inaczej obiektem funkcyjnym) nazywamy obiekt klasy, w której zdefiniowany został operator wywołania funkcji t.zn. operator( ). FUNKTORY Uwaga 1: Korzystanie z obiektu funkcyjnego jest składniowo identyczne z wywołaniem funkcji, dlatego wszędzie tam, gdzie parametrem funkcji ma być funkcja dostarczana jako argument, moŜna uŜyć funktora. Tomasz Marks - Wydział MiNI PW -15- Tomasz Marks - Wydział MiNI PW -16- Funktory (2) Funktory (3) Zwykłą funkcję Uwaga 2: typ funkcja ( argumenty ) { TreśćFunkcji } Funktory są obiektami klas, w których oprócz operator() mogą być zdefiniowane inne metody i pola składowe, w tym konstruktory. Zatem: moŜna "przekształcić" na funktor wg schematu: - funktory mogą posiadać stan wewnętrzny, który moŜe być inicjowany, odczytywany i modyfikowany; class Funktor { public: typ operator ( ) ( argumenty ) const { TreśćFunkcji } } funkcja; Tomasz Marks - Wydział MiNI PW - moŜe jednocześnie istnieć wiele funktorów (obiektów) tej samej klasy (realizujących takie same obliczenia), ale róŜniących się stanem wewnętrznym; - w przypadku zwykłych funkcji namiastką stanu moŜe być zmienna globalna lub lokalna dla funkcji zm. statyczna. -17- Tomasz Marks - Wydział MiNI PW Funktory (4) -18- Funktory – przykład (1) #include <iostream> using namespace std; Uwaga 3: KaŜdy funktor posiada własny typ, co moŜe być pomocne przy korzystaniu z szablonów: - funktory będąc obiektami róŜnych klas mają róŜne typy, nawet jeśli ich operatory wywołania funkcji mają identyczne sygnatury; class Sum { int s; public: Sum ( ) : s(0) { } void operator ( ) ( int a ) { s += a; } int operator ( ) ( ) { return s; } }; int arr[ ] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, }; - zwykłe funkcje o identycznych sygnaturach są z punktu widzenia szablonów nierozróŜnialne. Tomasz Marks - Wydział MiNI PW void main ( ) { Sum parzyste, nieparzyste; for ( int i = 0; i < 10; ++i ) if ( i & 1 ) nieparzyste ( arr[ i ] ); else parzyste ( arr[ i ] ); cout << parzyste() << " " << nieparzyste(); } -19- Tomasz Marks - Wydział MiNI PW // 25 30 -20- Funktory – przykład (2) #include <iostream> using namespace std; class Arr { int n, m; double *A; public: Arr ( int i, int j ) : n( i ), m( j ) { A = new double [ n * m ]; } ~Arr ( ) { delete [ ] A; } double & operator ( ) ( int i, int j ) { return A[ i * m + j ]; } }; KONWERSJE i KONWERTERY void main ( ) { Arr A (3, 4); for ( int i = 0; i < 3; ++i ) for ( int j = 0; j < 4; ++j ) A( i, j ) = 10 * ( i + 1) + ( j + 1 ); for ( int i = 0; i < 3; ++i ) { cout << endl; for ( int j = 0; j < 4; ++j ) cout << A( i, j ) << " "; } } // 11 12 13 14 // 21 22 23 24 // 31 32 33 34 Tomasz Marks - Wydział MiNI PW -21- Tomasz Marks - Wydział MiNI PW Klasyfikacja konwersji (klasycznych) (1) Operatory konwersji (klasyczne) 1. Standardowe – mogą być wykonywane jawnie i niejawnie, np. - dana typu arytmetycznego lub znakowego -> inna dana typu arytmetycznego lub znakowego, - dana reprezentowana literałem 0 lub 0L -> dana wskazująca dowolnego typu, - dana dowolnego typu wskazującego -> dana typu void*, - ……………………………….. Dla typu docelowego TYPE operator konwersji jest opisywany wyraŜeniem postaci: TYPE ( wyr ) , gdzie TYPE - identyfikator typu, wyr - konwertowane wyraŜenie, ( TYPE ) wyr , gdzie TYPE - dowolna nazwa typu, wyr - konwertowane wyraŜenie, albo 2. Definiowalne – mogą być wykonywane jawnie i niejawnie: - wszystkie konwersje określone przez konstruktory jednoparametrowe i konwertery. Np. 3. Dopuszczalne (predefiniowane konwersje, których skutek moŜe zaleŜeć od implementacji) – muszą być wykonywane jawnie, np. - dana całkowita -> dana wyliczeniowa, - dana typu wskazującego -> dana innego typu wskazującego (z wyłączeniem przypadku void*), - dana typu całkowitego <-> dana typu wskazującego, - wskazanie zmiennej ustalonej (albo ulotnej) -> wskazanie analogicznej zmiennej nieustalonej (albo nieulotnej), - ……………………………….. Tomasz Marks - Wydział MiNI PW -22- -23- k = (int) 12.5; // zapis poprawny k = int ( 12.5 ); // zapis poprawny p = (char *) z; // zapis poprawny p = char* ( z ); // BŁĄD! - char* jest nazwą typu // ale nie jest identyfikatorem Tomasz Marks - Wydział MiNI PW -24- Klasyfikacja konwersji (klasycznych) (2) Klasyfikacja konwersji (klasycznych) (3) 2. Konwersje odnośnikowe (referencyjne) JeŜeli typ docelowy jest odnośnikowy, to rezultatem konwersji będzie odnośnik typu TYPE zainicjowany argumentem konwersji. Taki odnośnik jest L-nazwą tylko wówczas, gdy spełnione są 2 warunki: (i) wyr jest L-nazwą zmiennej, (ii) istnieje konwersja &wyr na TYPE* (konwersja wskazania argumentu na wskazanie typu docelowego). 1. Konwersje wartościowe (nieodnośnikowe, niereferencyjne) JeŜeli typ docelowy nie jest odnośnikowy, to rezultatem konwersji będzie zmienna typu TYPE zainicjowana daną powstałą z przekształcenia danej o wartości wyr na daną typu TYPE. Zmienna taka nie jest L-nazwą. Np. double X = 1.5; Np. //(int) X = 12; cout << (int) X; const int X = 10; X = 12 // BŁĄD! - X ma atrybut const (int &) X = 20; // O.K. int & tmp = *(int *) & X; // (zmienna tmp skojarzona jest z obszarem // pamięci przydzielonym na zmienną X) cout << X; // wyprowadzi 20 albo 10 (!!!) // BŁĄD! - (int) X nie jest L-nazwą // O.K. - wyprowadzi 1 // rezultatem konwersji jest zmienna tymczasowa, // którą moŜna zdefiniować instrukcją: int tmp = 1; . Tomasz Marks - Wydział MiNI PW -25- Konwertery (1) - Definicja -26- Konwertery (2) – przykład definiowania class CMPLX { double Re, Im; public: CMPLX ( double ); // konwersja double CMPLX …………………….. operator double ( ); // konwersja CMPLX double operator const char * ( ); // konwersja CMPLX const char * …………………….. }; Konwerterem definiowanego typu Class do typu docelowego TYPE jest bezparametrowa metoda o deklaracji Class :: operator TYPE ( ); w której TYPE jest nazwą (!) typu nie zawierającą nawiasów. Wykonanie w ciele konwertera instrukcji return wyr ; skutkuje udostępnienien zmiennej typu TYPE o wartości (TYPE) wyr CMPLX :: CMPLX ( double re ) : Re(re), Im(0) { } CMPLX :: operator double ( ) { return Re; } UWAGA. Wymaga się, by konwersja typu wyraŜenia wyr do TYPE istniała. Tomasz Marks - Wydział MiNI PW Tomasz Marks - Wydział MiNI PW CMPLX :: operator const char * ( ) { if ( Re == 0 && Im == 0 ) return "Zero"; if ( Im == 0 ) return "Real"; if ( Re == 0 ) return "Imag"; return "Cmplx"; } -27- Tomasz Marks - Wydział MiNI PW -28- Konwertery (3) – przykład uŜycia "Nowe" operatory konwersji (1) #include "cmplx.h" typedef const char * ConstCharPtr; Obecnie zaleca się stosowanie "nowych" operatorów konwersji, zapisywanych z wykorzystaniem słów kluczowych: void main ( ) { CMPLX X(1.5,2.3), Y(5), Z; double a, b, c; const char *A, *B, *C; a = (double) X; b = double (X); c = X; cout << a << b << c; // jawne uŜycie konwertera na double // jawne uŜycie konwertera na double // niejawne uŜycie konwertera na double // 1.5 1.5 1.5 A = (const char *) Y; //A = const char * (Y); B = ConstCharPtr (Y); C = Y; cout << A << B << C; // jawne uŜycie konwertera na const char * // BŁĄD! - const char * nie jest identyfikatorem // jawne uŜycie konwertera na const char * // niejawne uŜycie konwertera na const char * // Real Real Real X = (CMPLX) a; Y = CMPLX (a); Z = a; cout << X << Y << Z; // jawne uŜycie konwersji konstruktorowej // jawne uŜycie konwersji konstruktorowej // niejawne uŜycie konwersji konstruktorowej // [1.5,0] [1.5,0] [1.5,0] static_cast reinterpret_cast const_cast dynamic_cast UŜycie "nowych" operatorów konwersji ma postać: xxxxx_cast < typ_docelowy > ( konwertowane_wyraŜenie ) } Tomasz Marks - Wydział MiNI PW -29- "Nowe" operatory konwersji (2) -30- "Nowe" operatory konwersji (3) static_cast – wykorzystywany do konwersji typów spokrewnionych - typu zmiennopozycyjny -> typu całkowity, - typ wyliczeniowy -> typ całkowity, - typ wskaźnikowy -> inny spokrewniony typ wskaźnikowy, …… Np. int x = static_cast<int>(22.6); PhVectorB *p = static_cast<PhVectorB*>(Vec); int *p = static_cast<int*>(malloc(20)); const_cast – wykorzystywany do usuwania kwalifikatora const Np. const int x = 10; const_cast <int&> ( x ) = 20; dynamic_cast – wykorzystywany do konwersji kontrolowanej w czasie wykonywania programu. Np. Zwierz* pZ; Pies P("Burek"); Ssak D("Umo"); pZ = dynamic_cast <Zwierz*> ( &P ); // pZ = wskazanie na P pZ = dynamic_cast <Zwierz*> ( &D ); // pZ = 0, jeśli niepoprawne Zwierz& rZ = dynamic_cast <Zwierz&> ( *pZ ); // odrzuci wyjątek bad_cast, jeśli niepoprawne reinterpret_cast – wykorzystywany do konwersji typów niespokrewnionych - typ całkowity -> typ wskaźnikowy, - typ wskaźnikowy -> inny niespokrewniony typ wskaźnikowy, …… Np. io_Unit* we = reinterpret_cast<io_Unit*>(0x123e); Tomasz Marks - Wydział MiNI PW Tomasz Marks - Wydział MiNI PW -31- Tomasz Marks - Wydział MiNI PW -32- Koniec wykładu 9. Tomasz Marks - Wydział MiNI PW -33-