Exceptiile sunt niste situatii deosebite ce pot aparea pe parcursul executiei unei aplicatii. Exista exceptii sincrone si exceptii asincrone. Exceptiile sincrone sunt usor de detectat: impartire prin 0, un fisier nu a putut fi deschis, etc. Exceptiile asincrone sunt mult mai greu de detectat si de tratat. Exceptiile sunt in general erori ce pot aparea in timpul functionarii unui program. O data detectate, exceptiile trebuie tratate. Exemple de masuri:
- se afiseaza mesaje pe ecran sau in log
- se reincearca executia unei secvente de program sau se reporneste executia
In limbajul C, tratarea exceptiilor poate fi facuta cu ajutorul unor functii din fisierul antet
setjmp.h
. Pentru tratarea exceptiilor avem nevoie de tipuri de datejmp_buf
, functiasetjmp
si functialongjmp
. Intr-o variabila de tipjmp_buf
poate fi incarcata starea programului din momentul apelului functieisetjmp
. Prin starea programului intelegem stiva de executie a aplicatiei. La primul apel al functieisetjmp
, functia returneaza valoarea0
. Cu ajutorul functieilongjmp
ne putem intoarce la linia in caresetjmp
a salvat starea executiei, moment in care se reincarca starea executiei salvata. Cu alte cuvinte, apelullongjmp
este echivalent cu incarcarea stivei de executie dintr-o variabilajmp_buf
si ungoto
la linia in caresetjmp
a salvat starea programului. Cu ajutorul acestor doua functii este simulata functionalitatea unei instructiuni de tipultry catch
impreuna cuthrow
, care nu exista in C.
jmp_buf stare;
float div(float x, float y)
{
if(!y)
long_jmp(stare,1); // throw
else
return x/y;
}
void main()
{
float a,b;
cin>>a>>b;
if(!setjmp(stare))
{
cout<<"Impartire = "<< div(a,b) << endl;
}
else
{
cerr<"Exceptie"<<endl;
}
}
La executie, cand se ajunge prima oara la functia setjmp
este salvata starea programului. Functie setjmp
returneaza 0
inseamna ca se trece la afisarea cu cout
. Daca in functia div
apelata, al doilea parametru are valoarea 0
executia se incheie in functia div
, se incarca starea salvata si se face un salt la linia in care este apelata setjmp
. La al doilea apel al functiei setjmp
este returnata valoarea 1
si executia continua pe ramura else
cu mesajul de eroare.
In limbajul C++ putem trata exceptiile cu ajutorul functiilor mostenite din C, dar, in C++ au fost introduse doua instructiuni pentru tratatea exceptiilor: throw
si try ... catch
. La detectarea unei exceptii, instructiunea throw
intrerupe executia si arunca o valoare ce este captata in functie de tipul ei pe una dintre ramurile catch
daca exceptia a aparut in blocul try
.
Vrem sa calculam loga(b)
:
double logaritm(double x, double y) // logx(y)
{
if(x<=0)
throw 1;
if(y<=0)
throw 2;
if(x==1)
throw "Baza nu poate fi 1.";
return log (y) / log (x);
}
void main()
{
double a,b;
cin>>a>>b;
try
{
cout<<"log = "<<logaritm(a,b)<<endl;
}
catch(int v)
{
if(v==1)
cerr<< "Baza nu este pozitiva.";
if(v==2)
cerr<< "Argumentul nu este pozitiv";
}
catch(const char* msg)
{
cerr<<msg<<endl;
}
catch(...)
{
cerr<< "Alta eroare"<<endl;
}
}
Dupa citirea valorilor a
si b
se incearca executia codului din blocul try
. Daca nu au aparut exceptii (aruncate cu o instructiune throw
), mesajul este trimis in cout
(pe ecran). Daca in blocul try
apare o exceptie, executia se incheie, mesajul neafisandu-se, iar executia continua pe una dintre ramurile catch
. Daca x<=0
sau y<=0
atunci este aruncata o valoare de tip int
, (1 sau 2), in consecinta executia este intrerupta si se revine la prima instructiune try...catch
, unde executia continua pe ramura catch
cu parametru int
, deoarece valoarea aruncata este de tipul int
(1 sau 2). Daca x=1
executia se incheie aruncandu-se un mesaj, practic, aruncandu-se adresa de memorie a mesajului respectiv. Executia continua in aceasta situatie pe ramura catch
cu valoare const char*
, unde se primeste adresa de memorie a textului aruncat.
In functie de tipul valorii aruncate de o instructiune throw
si de tipurile de date ale ramurilor catch
se incearca potrivirea cea mai buna. (short int
-> int
, int
-> float
). Daca valoarea aruncata nu poate fi convertita la niciunul din tipurile ramurilor catch, executia continua pe ramura catch(...)
, daca exista, unde este tratata sau exceptia ramane netratata, rezultand eroare la executie: Unhandled exception.
.
Exista, in functie de compilator, si alte posibile ramuri ale instructiunii try...catch
. De exemplu, Visual C++ putem sa avem ramuri de tip except
sau final
, dar nu sunt standard. In Java exista final
standard.
Instructiunea throw
poate fi apelata fara valoare. La intalnirea unui throw
fara valoare, daca exista o valoare aruncata si inca netratata, ea este aruncata pentru a fi tratata in urmatoarea instructiune try...catch
.
float a,b;
cin>>a>>b;
try
{
try
{
if(!b)
throw 0;
cout<<a<<b<<endl;
}
catch(int v)
{
cerr<< "Eroare"<<endl;
throw;
}
}
catch(int v)
{
cerr<< "Impartire la 0."<<endl;
}
La a doua instructiune try...catch
, daca se intalneste situatia b=0
se decide ca exceptia sa nu fie tratata aici, aruncandu-se tratarea ei intr-o instrunctiune try...catch
de mai sus, adica, pe ramura catch
a primei instructiuni try...catch
. Aruncarea tratarii pe un nivel superior se face cu throw;
.
Desi tratarea exceptiilor cu ajutorul instructiunilor try...catch
este moderna si eleganta, codul din corpul try
se executa mai incet, comparativ cu situatia in care nu ar fi introdus intr-un astfel de corp. De aceea, folosirea instructiunilor try...catch
este preferabil de a fi editata, acolo unde avem nevoie de viteza mare de executie.
Foarte multi programatori de C++ evita complet utilizarea instructiunilor try...catch
.
In momentul in care se scrie o aplicatie cu tratare de exceptii se prefera scrierea de clase speciale pentru exceptii, chiar ierarhii. Intr-u obiect de tip exceptie se memoreaza detalii despre exceptia respectiva in mai multe campuri. In alte limbaje, precum Java, aceasta practica este absolut fireasca tocmai din cauza ca Java se doreste a fi un limbaj sigur.