Tratarea exceptiilor permite rezolvarea in mod unitar a erorilor de executie.
Mecanismul de tratare a exceptiilor din C++ ofera posibilitatea ca o problema
care apare intr-o functie si aceasta nu o poate rezolva, sa fie definita
ca o exceptie, cu speranta ca functia apelanta va trata aceasta problema. O
functie care doreste sa trateze astfel de probleme va indica acest lucru prin
captarea exceptiei respective, iar codul care se executa la captare se numeste
rutina de tratare a exceptiei (exception handler). l9t20tp
Ca exemplu, se considera modul in care poate fi tratata o depasire de
dimensiune in clasa Vector.
class VectorA int* p; int sz; public: class Range AS; // exceptie la depasire dimens. int& operatorai(int i);
// ....
S; int& Vector::operatorai(int i)A if (0 <= i && i < sz) return paii; throw Range(); return pa0i; // numai pt. evitare erori comp.
S void f(Vector &v)A try A fv(v); // apelul unei functii
S catch(Vector::Range)A
// Rutina de tratare a exceptiei
// Vector::Range
// In acest punct se ajunge numai daca
// in functia fv(v) s-a apelat operatorul ai
// cu un indice in afara domeniului
S
S
Constructia:
catch ( /*…..…… */)A
// …….
S
este numita rutina de tratare a exceptiei (exception handler). Ea este apelata
imediat dupa un bloc prefixat de cuvantul cheie try sau dupa o alta rutina
de tratare a unei exceptii. Paranteza care urmeaza cuvantului cheie catch
contine o declaratie care este folosita ca un argument: aceasta declaratie specifica
tipul exceptiei de care se ocupa rutina de tratare a erorii si, uneori, numele
argumentului.
Daca o exceptie (ceea ce insemna o eroare) apare intr-un bloc prefixat
de specificatorul try, ea este lansata de catre instructiunea throw, dupa care
este captata intr-o rutina de tratare a exceptiilor (deci in blocul
instructiunii catch) si prelucrata. Blocul try trebuie sa contina acea sectiune
a programului in care se doreste sa fie cautate erorile. El poate cuprinde
cateva instructiuni intr-o functie sau chiar intregul corp
al functiei main(), ceea ce inseamna urmarirea erorilor in intregul
program.
Atunci cand o functie genereaza o exceptie, ea nu isi mai continua
executia ca in situatia in care ar apela o functie de tratare a
erorii, din care se revine printr-o instructiune return, ci executia este captata
in rutina de tratare, dupa care se continua programul cu instructiunile
care urmeaza rutinei de tratare a exceptiei.
8.1 Discriminarea exceptiilor
In mod obisnuit, intr-un program pot apare mai multe tipuri de
erori de executie (run-time errors) si fiecare tip de eroare poate fi asociat
unei exceptii cu un nume distinct. Captarea exceptiilor este executata de mai
multe blocuri catch, care pot fi asociate unuia sau mai multor blocuri try.
Forma generala pentru un bloc try urmat de mai multe blocuri catch care trateaza
diferite tipuri de exceptii este urmatoarea: try A
// bloc try
S catch (tip_arg1 arg1) A
// bloc de tratare a exceptiei
// de tipul tip_arg1
S catch (tip_arg2 arg2) A
// bloc catch de tratare a exceptiei
// de tipul tip_arg2
S
………………………………….
catch (tip_argn argn) A
// bloc de tratare a exceptiei
// de tipul tip_argn
S
O astfel de constructie este asemanatoare unei instructiuni switch, in
care selectia unei rutine (un bloc catch) se face in functie de tipul
exceptiei lansate in blocul try, fara sa fie necesara o instructiune break.
Daca in blocul try nu apare nici o exceptie, toate blocurile catch consecutive
acestuia sunt ignorate. La aparitia unei exceptii, controlul programului este
transferat rutinei de tratare a exceptiei corespunzatoare tipului acesteia,
toate celelate blocuri catch fiind ignorate. Dupa executia unei rutine de tratare
a exceptiei, programul fie continua cu executia instructiunilor urmatoare blocurilor
catch, fie se termina, daca rutina respectiva a apelat o functie exit() sau
abort().
Daca se lanseazeaza o exceptie pentru care nu exista nici o rutina de tratare
(bloc catch), atunci este posibil sa apara o executie anormala a programului.
? Exemplul 8.1
void fd()A char tip;
while (tip !='q')A tryA int argi; double argd; char argc; cout << “Start bloc try\n”; cout << "Introduceti tipul si argumentul exceptiei: "; cin >> tip; switch (tip)A case 'i': cin >> argi; cout << "Lansare exceptie int\n"; throw argi; break; case 'd': cin >> argd; cout<<"Lansare except. double\n"; throw argd; break; case 'c': cin >> argc; cout<<"Lansare exceptie char\n"; cout.flush(); throw argc; break;
S cout << "Nu s-a lansat exceptie\n";
S catch(int i)A cout << "Captare exceptie int i = " << i <<endl;
S catch(double d)A cout << "Captare exceptie double d = " << d <<endl;
S cout << "End bloc try-catch\n";
S
S
In aceasta functie se poate selecta tipul si argumentul unei exceptii
prin valori introduse de la consola. La selectia tipului de argument int (prin
introducerea de la tastatura a caracterului i), se lanseaza o exceptie pentru
tipul de date integer, cu o valoare a argumentului egala cu valoarea citita
de la consola. Aceasta exceptie este captata de instructiunea catch(int i),
care primeste in argumentul i valoarea cu care a fost lansata exceptia
de tip integer si o afiseaza.
Cu acest program se pot testa diferitele situatii care pot apare in executia
blocurilor try-catch: selectarea uneia din rutinele de tratare a exceptiilor
(unul din blocurile catch) in functie de tipul exceptiei lansate in
blocul try, ignorarea tuturor blocurilor catch daca in blocul try nu se
lanseaza nici o exceptie, precum si terminarea anormala a programului in
situatia in care s-a lansat o exceptie, dar nu exista o rutina de tratare
pentru tipul exceptiei lansate.
Liniile de afisare care se pot obtine la executia functiei fd(), pentru diferite
situatii selectate, sunt prezentate mai jos:
Start bloc try
Introduceti tipul si argumentul exceptiei: i 2
Lansare exceptie int
Captare exceptie int i = 2
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul exceptiei: d 3.5
Lansare exceptie double
Captare exceptie double d = 3.5
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul exceptiei: g
Nu s-a lansat exceptie
End bloc try-catch
Start bloc try
Introduceti tipul si argumentul exceptiei: c a
Lansare exceptie char
Abnormal program termination
?
In mod asemanator, in clasa Vector pot fi tratate mai multe tipuri
de erori: in afara de eroarea de depasire a dimensiunii vectorului, poate
fi tratata si eroarea care apare datorita unei valori inacceptabile la constructia
vectorului:
class VectorA int* p; int sz; enum Amax=1000S; public: class Range AS; // exceptie la depasire domeniu class Size AS; // exceptie la constructie
Vector(int s); int& operatorai(int i);
// ....
S;
Vector::Vector(int s)A if (s < 0 || max < s) throw Size(); sz = s; p = new intasi;
S
Asa cum s-a aratat mai sus, operatorul de indexare ai al clasei Vector va
lansa exceptia de tip (clasa) Range daca este apelat cu un indice in afara
domeniului. In mod similar, constructorul Vector::Vector va lansa o exceptie
de tip Size, daca este apelat cu un argument cu o valoare inacceptabila.
In utilizarea clasei Vector se poate discrimina intre cele doua
exceptii prin adaugarea a doua rutine de tratare a exceptiilor dupa un bloc
try:
void fex(Vector &v)A try A fv(v); // apelul unei functii cu arg. v
S catch(Vector::Range)A
// Rutina de tratare a exceptiei
// Vector::Range
// In acest punct se ajunge numai daca
// in functia fv(v) s-a apelat operatorul ai
// cu un indice in afara domeniului
S catch(Vector::Size)A
// Rutina de tratare a exceptiei
// Vector::Range
// In acest punct se ajunge daca in functia
// fv(v) s-a apelat un constructor
// cu un argument cu o valoare inacceptabila
S
S
8.2 Modalitati de tratare a exceptiilor
In C++ exista multe modalitati de tratare a exceptiilor, dintre care o
parte vor fi descrise in continuare.
8.2.1 Tratarea tuturor exceptiilor
In Exemplul 8.1 a fost prezentata o situatie in care nu era tratata
o exceptie de un anumit tip, situatie care provoaca executia anormala a programului,
atunci cand o astfel de exceptie este lansata in blocul try corespunzator.
Acstfel de erori pot fi evitate daca se foloseste o constructie de captare a
tuturor erorilor, care are urmatoarea sintaxa:
catch(…)A
// tratare generala exceptii
S
Instructiunea catch(…) indica captarea oricaror exceptii (corespunzatoare
tuturor tipurilor de date). Tratarea exceptiilor in astfel de situatii
poate fi o tratare generala.
Mai este posibila combinarea uneia sau mai multor instructiuni catch de captare
a unor tipuri specifice de exceptii cu o instructiune de captare a tuturor exceptiilor,
combinatie prin care se urmareste tratarea diferentiata a unor anumite tipuri
de exceptii, toate celelalte fiind tratate global. Astfel de constructii permit
evitarea terminarii anormale a programului.
Daca in functia fd() din Exemplul 8.1 se adauga dupa blocul try o instructiune
catch(…) de captare generala, atunci nu mai apare eroarea de executie
la lansarea unei exceptii netratate specific:
//………………………….. tryA
// acelasi bloc ca in Exemplul 8.1
S catch(int i)A cout << "Captare exceptie int i = " << i <<endl;
S catch(double d)A cout << "Captare exceptie double d = " << d <<endl;
S catch(…)A cout << "Captare exceptie oarecare\n”;
S
//…………………………….
La lansarea exceptiei cu argument de tip caracter (throw argc) se transfera
controlul rutinei de captare generala a exceptiilor (care, in exemplul
de mai sus afiseaza doar un mesaj la consola) si nu mai apare executia anormala
a programului.
8.2.2 Transferul de informatii catre rutina de tratare a exceptiei
O exceptie poate fi captata prin specificarea tipului ei. Totusi, ceea ce
se lanseaza (throw) in momentul aparitiei unei erori nu este un tip de
date ci un obiect de tipul respectiv, care este construit la apartia erorii.
De aceea, in definirea unui tip exceptie (clasa) se pot adauga date membre
care pot fi setate la constructia obiectului lansat si utilizate in rutina
de tratare a exceptiilor pentru a identificarea conditiilor de aparitie a erorii.
De exemplu, clasa Range de definire a exceptiei de depasire a dimensiunii vectorului
poate sa contina o data membra index care memoreaza valoarea indicelui care
a produs depasirea si o functie publica getindex() care permite citirea acestei
valori, astfel incat valoarea indicelui care a produs depasirea
poate fi aflat si, eventual, afisat in rutina de tratare a erorii. La
fel se poate proceda si pentru clasa Size. Exemplul urmator completeaza clasa
Vector si clasele exceptie definite de aceasta si ilustreaza crearea si utilizarea
obiectelor de clasa execptie.
? Exemplul 8.2
In clasa Vector sunt definite clasele exceptie Range si Size care permit
stocarea si regasirea unor informatii prin intermediul obiectelor lansate la
aparitia unei erori de un anumit tip. La apelul unei functii operator de indexare
ai a clasei Vector cu o valoare a indicelui i < 0 sau i >= sz, se lanseaza
exceptia de depasire a domeniului prin construirea unui obiect din clasa Range,
cu argumentul i: Range(i). Obiectul lansat memoreaza (prin constructie) aceasta
valoare. Rutina de tratare a exceptiei de depasire a domeniului are ca argument
un obiect r de clasa Vector::Range, care este chiar obiectul lansat la aparitia
erorii in functia operator de indexare. Folosind functiile membre ale
acestui obiect, rutina de tratare a exceptiei poate regasi informatii privind
conditia de aparitie a erorii.
class VectorA int* p; int sz; enum Amax=1000S; public:
Vector(int s); class Range A // clasa exceptie int index; public:
Range(int i)Aindex = i;S int getindex()Areturn index;S
S; class Size A // clasa exceptie int dim; public:
Size(int d)Adim=d;S int getdim() Areturn dim;S
S;
int& operatorai(int i); int getsize() Areturn sz;S
// ....
S;
Vector::Vector(int s)A if (s < 0 || max < s) throw Size(s); sz = s; p = new intasi;
S int& Vector::operatorai(int i)A if (0 <= i && i < sz) return paii; throw Range(i); return pa0i; // numai pentru evitarea erorii
// de compilare
S void fv(Vector& v, int d)A
Vector v2(d); for (int i=0;i<d;i++)A v2aii = vaii = i;
S
S void fex(Vector &v, int d)A tryA fv(v, d);
S catch(Vector::Range r)A cerr << "Indicele " << r.getindex()
<< " in afara domeniului\n"; return;
S catch(Vector::Size s)A cerr << "Dimensiunea " << s.getdim()
<< " depaseste valoarea admisa\n"; return;
S cout << "Vectori OK" << endl; return 0;
S
void main()A
Vector v1(100); int d; cout << "Introduceti dimensiunea: "; cin >> d;
while(fex(v1,d))A cout << "Introduceti dimensiunea: "; cin >> d;
S
S
La executia acestui program se creaza mai intai un vector de dimensiune
100, dupa care se citeste de la tastatura dimensiunea unui alt vector. In
functie de valoarea introdusa la consola, pot apare trei situatii distincte.
Daca dimensiunea introdusa d este mai mica sau egala cu dimensiunea primului
vector (100), atunci nu apare nici-o eroare de executie, programul afiseaza
un mesaj la consola dupa care se opreste.
Daca dimensiunea introdusa d este mai mare decat dimensiunea primul vector
(100), dar mai mica decat valoarea maxim admisibila pentru constructia
vectorilor (max=1000), atunci apare o eroare de depasire a domeniului in
functia operator de indexare ai si se lanseaza exceptia prin construirea unui
obiect de clasa Range, cu argument al constructorului egal cu valoarea primului
indice care depaseste domeniul. Aceasta valoare este regasita in functia
de tratare a exceptiei, care o afiseaza la consola. Mesajele care apar la consola
in aceasta situatie sunt:
Introduceti dimensiunea: 200
Indice 100 in afara domeniului
Introduceti valoarea:
Daca dimensiunea introdusa d este mai mare decat valoarea maxima admisibila
(1000), atunci apare o eroare in momentul constructiei obiectului v2 de
tip Vector in functia fex(). In constructorul Vector::Vector, la
aparitia unei astfel de erori se lanseaza o exceptie prin construirea unui obiect
de tip Size, cu valoare a argumentului d, valoare care este memorata in
data membra privata dim a clasei Size. Aceasta valoare este regasita in
functia de tratare a exceptiei, care o afiseaza la consola. Mesajele care apar
la consola in aceasta situatie sunt:
Introduceti dimensiunea: 1001
Dimensiunea 1001 depaseste valoarea admisa
Introduceti dimensiunea:
?
8.2.3 Imbricarea rutinelor de tratare a exceptiilor
Limbajul C++ admite constructii imbricate de tratare a execptiilor, ca de exemplu:
tryA
// bloc try
S catch (ExceptionX)A try A
// o rutina care incearca
// tratarea exceptiei
S catch(ExceptionX)A
// o alta rutina de tratare
// care este lansata daca prima rutina
// a provocat o noua exceptie
S
S
O exceptie este tratata de rutina imediat urmatoare blocului try, dar si in
aceasta tratare poate fi lansata o noua exceptie (eventual de acelasi tip),
de care se va ocupa un nou bloc catch, imbricat in primul bloc try. Desi
posibile, astfel de constructii imbricate sunt rar utilizate sau utile.
8.2.4 Gruparea exceptiilor
De cele mai multe ori, exceptiile se pot grupa in familii, in
functie de categoria la care se refera. De exemplu, erori de depasire superioara,
depasire inferioara de impartire la zero si altele pot fi grupate in
categoria de erori in operatii aritmetice cu numere flotante; erori de
deschidere fisier, inchidere fisier, erori de citire sau erori de scrire,
pot fi grupate ca erori in operatii cu fisiere, s.a.md.
In astfel de situatii, rutinele de tratare a exceptiilor sunt destul de
asemanatoare si se pot implementa intr-un mod unitar. Implementarea unitara
a grupurilor de exceptii se poate face in doua modalitati: prin enumerare
si prin organizarea exceptiilor in ierarhii de clase folosind mostenirea
si functiile virtuale.
In abordarea prin enumerare a grupurilor de exceptii se defineste un tip
enumerare ale carui posibile valori sunt numele tuturor exceptiilor din grup,
iar selectia intre rutinele specifice se face printr-o instructiune switch:
class Overflow A S; class Underflow A S; class Dividebyzero A S;
enum Mathenum AOverflow, Underflow, DividebyzeroS; void fenum()A tryA
// lansare exceptii
S catch (Mathenum m)A switch (m)A case Overflow:
// tratare depasire superioara break; case Underflow:
// tratare depasire inferiora break; case Dividebyzero:
// tratare impartire la 0 break;
S
S
S
O astfel de organizare a grupurilor de exceptii, desi posibila, este voluminoasa
si cu mari riscuri de a a fi omise unele situatii, care, bineinteles,
vor provoca terminarea anormala a programului. In plus, la adaugarea unei
noi exceptii intr-un grup de exceptii ale unei biblioteci, ar fi necasara
recompilarea tuturor modulelor care contin rutine de captare a exceptiilor.
Acest lucru este, bineinteles, imposibil si de aceea ar fi preferabil
sa nu se adauge noi exceptii intr-o biblioteca daca tratarea grupurilor
de exceptii este implementata prin enumerarea acestora.
O solutie mai eleganta si mai robusta o reprezinta organizarea exceptiilor in
ierarhii de clase. De exemplu:
class Matherr A S; class Overflow : public MatherrA S; class Underflow : public Matherr A S; class Dividebyzero : public Matherr A S;
void fder()A tryA
// lansare exceptii
S catch (Overflow)A
// tratare depasire superioara
S catch (Matherr)A
// tratare orice exceptie Matherr
// care nu este Overflow
S
S
In acest exemplu, dintre toate exceptiile care pot fi lansate in
blocul try, exceptia Overflow are o tratare speciala, in timp ce toate
celelalte exceptii derivate din clasa Matherr au o tratare globala prin rutina
catch(Matherr), deci nu vor provoca terminarea anormala a programului.
Din exemplele prezentate pana acum se poate observa ca exceptiile pot
fi definite global, pentru intregul program, cum sunt exceptiile Matherr
sau Overflow, sau pot fi exceptii locale unei clase, cum sunt exceptiile Range
sau Size, definite in clasa Vector. Pentru astfel de exceptii, in
instructiunea catch trebuie sa fie specificat domeniul clasei exceptie: catch
(Vector::Range).