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-