Gestire le eccezioni
Quanto abbiamo visto chiaramente non e` sufficiente, non basta poter sollevare
(segnalare) una eccezione ma e` necessario poterla anche catturare e gestire.
L'intenzione di catturare e gestire l'eventuale eccezione deve essere segnalata
al compilatore utilizzando un blocco try:
#include < iostream >
using namespace std;
int Divide(int a, int b) throw(char*) {
if (b) return a/b;
throw "Errore";
}
int main() {
cout << "Immettere il dividendo: ";
int a;
cin >> a;
cout << endl << "Immettere il divisore: ";
int b;
cin >> b;
try {
cout << Divide(a, b);
}
/* ... */
}
Utilizzando try e racchidendo tra parentesi graffe (le parentesi
si devono utilizzate sempre) il codice che puo` generare una eccezione si segnala
al compilatore che siamo pronti a gestire l'eventuale eccezione.
Ci si potra` chiedere per quale motivo sia necessario informare il compilatore
dell'intenzione di catturare e gestire l'eccezione, il motivo sara` chiaro in
seguito, al momento e` sufficiente sapere che cio` ha il compito di indicare
quando certi automatismi dovranno arrestarsi e lasciare il controllo a codice
ad hoc preposto alle azioni del caso.
Il codice in questione dovra` essere racchiuso all'interno di un blocco
catch che deve seguire il blocco try:
#include < iostream >
using namespace std;
int Divide(int a, int b) throw(char*) {
if (b) return a/b;
throw "Errore, divisione per 0";
}
int main() {
cout << "Immettere il dividendo: ";
int a;
cin >> a;
cout << endl << "Immettere il divisore: ";
int b;
cin >> b;
cout << endl;
try {
cout << "Il risultato e` " << Divide(a, b);
}
catch(char* String) {
cout << String << endl;
return -1;
}
return 0;
}
Il generico blocco catch potra` gestire in generale solo una categoria di eccezioni
o una eccezione generica. Per fornire codice diverso per diverse tipologie di
errori bisognera` utilizzare piu` blocchi catch:
try {
/* ... */
}
catch(Type1 Id1) {
/* ... */
}
catch(Type2 Id2) {
/* ... */
}
/* Altre catch */
catch(TypeN IdN) {
/* ... */
}
/* Altro */
Ciascuna catch e` detta exception handler e riceve
un parametro che e` il tipo di eccezione che viene gestito in quel blocco. Nel
caso generale un blocco try sara` seguito da piu` blocchi catch, uno per ogni
tipo di eccezione possibile all'interno di quel try. Si noti che le catch devono
seguire immediatamente il blocco try.
Quando viene generata una eccezione (throw ) il controllo
risale indietro fino al primo blocco try. Gli oggetti staticamente allocati
(che cioe` sono memorizzati sullo stack) fino a quel momento nei blocchi da
cui si esce vengono distrutti invocando il loro distruttore (se esiste). Nel
momento in cui si giunge ad un blocco try anche gli oggetti staticamente allocati
fino a quel momento dentro il blocco try vengono distrutti ed il controllo passa
immediatamente dopo la fine del blocco.
Il tipo dell'oggetto creato con throw viene quindi confrontato
con i parametri delle catch che seguono la try. Se viene trovata una catch del
tipo corretto, si passa ad eseguire le istruzioni contenute in quel blocco,
dopo aver inizializzato il parametro della catch con l'oggetto restituito con
throw . Nel momento in cui si entra in un blocco catch, l'eccezione
viene considerata gestita ed alla fine del blocco catch il controllo passa alla
prima istruzione che segue la lista di catch (sopra indicato con "/*
Altro */ ").
Vediamo un esempio:
#include < iostream >
#include < string.h >
using namespace std;
class Test {
char Name[20];
public:
Test(char* name){
Name[0] = '\0';
strcpy(Name, name);
cout << "Test constructor inside "
<< Name << endl;
}
~Test() {
cout << "Test distructor inside "
<< Name << endl;
}
};
int Sub(int b) throw(int) {
cout << "Sub called" << endl;
Test k("Sub");
Test* K2 = new Test("Sub2");
if (b > 2) return b-2;
cout << "exception inside Sub..." << endl;
throw 1;
}
int Div(int a, int b) throw(int) {
cout << "Div called" << endl;
Test h("Div");
b = Sub(b);
Test h2("Div 2");
if (b) return a/b;
cout << "exception inside Div..." << endl;
throw 0;
}
int main() {
try {
Test g("try");
int c = Div(10, 2);
cout << "c = " << c << endl;
} // Il controllo ritorna qua
catch(int exc) {
cout << "exception catched" << endl;
cout << "exception value is " << exc << endl;
}
return 0;
}
La chiamata a Div all'interno della main provoca una
eccezione nella Sub , viene quindi distrutto l'oggetto k
ed il puntatore k2 , ma non l'oggetto puntato (allocato dinamicamente).
La deallocazione di oggetti allocati nello heap e` a carico del programmatore.
In seguito alla eccezione, il controllo risale a Div , ma la chiamata
a Sub non era racchiusa dentro un blocco try e quindi anche Div
viene terminata distruggendo l'oggetto h . L'oggetto h2
non e` stato ancora creato e quindi nessun distruttore per esso viene invocato.
Il controllo e` ora giunto al blocco che ha chiamato la Div , essendo
questo un blocco try, vengono distrutti gli oggetti g e c
ed il controllo passa nel punto in cui si trova il commento.
A questo punto viene eseguita la catch poiche` il tipo dell'eccezione e` lo
stesso del suo argomento e quindi il controllo passa alla return
della main .
Ecco l'output del programma:
Test constructor inside try
Div called
Test constructor inside Div
Sub called
Test constructor inside Sub
Test constructor inside Sub 2
exception inside Sub...
Test distructor inside Sub
Test distructor inside Div
Test distructor inside try
exception catched
exception value is 0
Si provi a tracciare l'esecuzione del programma e a ricostruirne la storia,
il meccanismo diverra` abbastanza chiaro.
Il compito delle istruzioni contenute nel blocco catch costituiscono quella
parte di azioni di recupero che il programma deve svolgere in caso di errore,
cosa esattamente mettere in questo blocco e` ovviamente legato alla natura del
programma e a cio` che si desidera fare; ad esempio ci potrebbero essere le
operazioni per eseguire dell'output su un file di log. E` buona norma studiare
gli exception handler in modo che al loro interno non possano verificarsi eccezioni.
Nei casi in cui non interessa distinguere tra piu` tipologie di eccezioni, e`
possibile utilizzare un unico blocco catch utilizzando le ellissi:
try {
/* ... */
}
catch(...) {
/* ... */
}
In altri casi invece potrebbe essere necessari passare l'eccezione ad un blocco
try ancora piu` esterno, ad esempio perche` a quel livello e` sufficiente (o
possibile) fare solo certe operazioni, in questo caso basta utilizzare throw
all'interno del blocco catch per reinnescare il meccanismo delle eccezioni a
partire da quel punto:
try {
/* ... */
}
catch(Type Id) {
/* ... */
throw; // Bisogna scrivere solo throw
}
In questo modo si puo` portare a conoscenza dei blocchi piu` esterni della condizione
di errore.
|