Tratarea exceptiilor a fost adaugata limbajului in anul 1989. Ea reprezinta un mecanism de transfer al codului programului din punctul de aparitie al unei exceptii la un punct de tratare (handler) corespunzator. Acest mecanism consta dintr-un bloc try, o secventa de handlere asociate, o expresie throw si exceptia propriu-zisa (o variabila de tip predefinit sau abstract). De exemplu:
try{
int *p = new int[1000];
}
catch (std::bad_alloc&){/**/}
catch (std::bad_cast&){/**/}
Handler-ul corespunzator este invocat de o expresie throw dintr-un bloc try (sau dintr-o functie apelata dintr-un bloc try). O expresie throw este similara unei instructiuni return. O expresie throw vida consta din instructiunea throw fara operand si indica un rethrow (daca nici o exceptie nu este tratata, se apeleaza terminate()).O exceptie poate fi transferata handler-ului prin valoare sau prin referinta. Memoria pentru obiectele de tip exceptie este alocata de o maniera nespecificata; unele implementari utilizeaza o stiva dedicata. Pasarea exceptiei prin referinta asigura comportamentul polimorfic. Pasarea exceptiei prin valoare poate fi costisitoare deoarece obiectul poate fi distrus si reconstituit de cateva ori pana cand este atins handler-ul corespunzator!
Tipul exceptiei determina handler-ul care o va captura si trata. Regulile de potrivire a tipurilor exceptiilor permit doar un set limitat de conversii. Pentru o exceptie E si un handler care captureaza exceptii de tipul T sau T&, potrivirea este valida daca:
class Zerodivide{/**/};
int divide(int, int) throw (Zerodivide);
//poate genera doar exceptii de tipul Zerodivide!
O functie care NU genereaza exceptii se declara astfel:
bool equals(int, int) throw();
O functie precum
bool equals(int, int);
nu garanteaza nimic in legatura cu exceptiile: poate genera orice tip de exceptie, sau nu va genera exceptii!C++ aplica o politica prin care limbajul acorda incredere programatorului; astfel, el ignora un cod care pare sa violeze specificatia de exceptie, precum cel din exemplul urmator:
int f(); //f poate genera exceptii de orice tip
void g(int j)throw(){ //g nu genereaza exceptii
int result = f();
}
Daca f genereaza o exceptie, g va viola garantia de a nu genera exceptii; totusi, acest cod este legal! Desi astfel de situatii pot fi verificate la compilare, verificarea specificatiilor de exceptie se realizeaza numai la executie. Programatorul nu este fortat intr-o situatie de acest gen sa scrie cod try ...catch inutil, deoarece f poate fi, de exemplu, o functie C din biblioteca standard!C++ solicita concordanta specificarii exceptiilor in clasele derivate: supraincarcarea unei functii virtuale intr-o clasa derivata trebuie sa aiba o specificare de exceptie care este, cel putin, la fel de restrictiva precum specificarea de exceptie a functiei din clasa de baza. De exemplu:
class BaseEx{};
class DerivedEx: public BaseEx{};
class OtherEx{};
class A{
public:
virtual void f() throw (BaseEx);
virtual void g() throw (BaseEx);
virtual void h() throw (DerivedEx);
virtual void i() throw (DerivedEx);
virtual void j() throw (BaseEx);
};
class d: public A{
public:
virtual void f() throw (DerivedEx); //ok
virtual void f() throw (OtherEx); //error
virtual void g() throw (DerivedEx); //ok
virtual void i() throw (BaseEx); //error
virtual void j() throw (BaseEx, OtherEx); //error
};
Aceleasi reguli de concordanta se aplica pointerilor la functii: unui pointer la o functie care poseda o specificatie de exceptie i se poate atribui doar o functie care poseda o specificatie de exceptie identica sau mai restrictiva! In consecinta, unui pointer la o functie care nu are specificatie de exceptie nu i se poate atribui o functie care poseda o specificatie de exceptie!
void f(int) throw(Y);
void f(int) throw(Z); //eroare
typedef void (*PF)(int) throw (Exception); //eroare
Generarea unei exceptii dintr-un destructor este o tehnica periculoasa: destructorul poate sa fi fost invocat datorita unei alte exceptii, care determina iesirea obiectului din scop; in acest caz, mecanismul de tratare invoca terminate(). Daca trebuie totusi sa se genereze o exceptie dintr-un destructor, trebuie sa se verifice daca nu cumva este procesata, in mod curent, o exceptie necapturata inca (adica nu s-a intrat inca in handler-ul corespunzator). Se utilizeaza functia standard uncaught_exception() din headerul <stdexcept>:
class FileException{};
File::~File() throw (FileException){
if (close(file_handler)!=succes){
if (uncaught_exception() == true) return;
//este sigur sa se genereze o exceptie
throw FileException(); //semnalizam eroare
};
return; //succes
};
Totusi, un design mai bun trateaza exceptiile in destructor decat sa le lase sa se propage mai departe!
void cleanup() throw(int);
class C{
public:
~C();
};
C::~C(){
try {
cleanup();
}
catch (int){/*tratarea exceptiei */}
}
Pentru un obiect global, constructorul se apeleaza inainte de main(); o exceptie generata de constructor nu poate fi, in acest caz, capturata! Analog, distrugerea unui obiect global se realizeaza dupa terminarea lui main(); o exceptie generata de destructor nu poate fi, in acest caz, capturata!Limbajul C++ defineste o ierarhie de exceptii standard, derivate din std::exception. Se pot captura astfel aceste exceptii cu un singur catch:
catch(std::exception& ex){
//trateaza exceptiile de tipul std::exception si pe cele derivate din ea
}