Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Ingegneria
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Valeria Vittorini » 8.Gestione delle eccezioni. Concetti base


Il problema

In alcuni casi, chi scrive una libreria può rilevare errori a run-time, ma non sa come gestirli.

Chi usa la libreria, invece, sa come gestire tali errori ma non può rilevarli.

Soluzioni ad-hoc possono essere definite di volta in volta.

È necessario un meccanismo generale, semplice da usare ed elegante per gestire situazioni “eccezionali”

Esempio: overflow a seguito di somma

Nel prossimo esempio la funzione “somma” effettua la somma di due interi short.
L’operazione richiesta però può provocare un overflow, cioè il risultato dell’operazione può non essere rappresentabile all’interno dell’intervallo di rappresentazione degli interi short.
La funzione può effettuare un controllo mediante un costrutto if, e valutare – prima di eseguire la somma- se gli operandi sono tali da provocare un overflow.
Il controllo è effettuato utilizzando le costanti di libreria (<climits>) SHRT_MAX e SHRT_MIN (rispettivamente il massimo ed il minimo valore rappresentabile di tipo short).
Se la condizione espressa assume valore “true” allora la somma non può essere effettuata, la verità o la falsità della proposizione logica però ovviamente sarà nota solo a tempo di esecuzione. A questo punto la funzione come può gestire la situazione?

Mostra codice Mostra codice

Soluzione 1: termina il programma

  • Una possibile soluzione – mostrata nel codice seguente- consiste nell’introdurre una uscita forzata dalla funzione nel ramo then del costrutto di selezione, ad esempio mediante l’istruzione exit.
  • Ma sarebbe preferibile gestire l’eccezione, e non terminare il processo…
Mostra codice

Soluzione 2: restituisce un valore di errore

  • Una seconda soluzione può essere far restituire alla funzione un valore “particolare” in caso di errore, ad esempio – in un caso diverso- se la funzione dovesse ritornare la posizione di un elemento nel vettore potrebbe tornare come valore -1 (non è una posizione ammissibile).
  • Ma non sempre questo approccio è possibile.
  • Nel caso del nostro esempio, nessun valore del tipo short può essere usato per indicare una situazione eccezionale (perchè può sempre essere interpretato dall’utente della funzione come valore della somma…).
  • Si può pensare di segnalare l’errore utilizzando una variabile booleana passata per riferimento.
  • Ma anche questa soluzione non è soddisfacente se possono presentarsi più situazioni eccezionali da gestire da parte della stessa funzione (potrebbe essere necessario distinguere quale di esse si è verificata…).

Soluzione 3: restituisce un valore legale e setta una variabile globale

  • Allora si potrebbe usare una variabile globale (chiamata “errno” nell’esempio seguente).
  • Tale variabile viene modificata nel ramo else assegnandole un valore che rappresenti un “codice di errore”, in modo da poter distinguere tra diverse situazioni.
  • Cosa succede se il chiamante non controlla errno?
Mostra codice Mostra codice

Il meccanismo di gestione delle eccezioni

Fornisce un’alternativa alle tecniche tradizionali quando queste sono insufficienti e male applicate possono indurre il programmatore ad introdurre errori.

L’idea è che una funzione che trova un errore che non sa gestire lancia (throw) un’eccezione, nella speranza che il suo chiamante (diretto o indiretto) possa gestire il problema.

La gestione elle eccezioni:

  • è un meccanismo generale.
  • consente di separare il codice per la gestione dell’eccezione dal codice “ordinario”;
  • il programma diventa più leggibile.

Gestione delle eccezioni

  • Una funzione che vuole gestire un certo tipo di eccezione (handler) può farlo indicando che intende catturare (catch) quel tipo di eccezione.
  • Se viene lanciata un’eccezione e ripercorrendo a ritroso la catena di chiamanti non si incontra nessuna funzione che cattura l’eccezione, il programma viene terminato.

Gestione delle eccezioni

Nel caso del nostro esempio, la funzione “somma” lancia un’eccezione al verificarsi di una situazione che non sa gestire (l’overflow):

throw [oggetto]

  • Nell’esempio l’eccezione lanciata dalla funzione “somma” è la stringa di caratteri “Overflow”.
  • L’esecuzione dell’istruzione “throw” provoca la terminazione della funzione somma.
Mostra codice

Gestione delle eccezioni

La funzione “main” vuole gestire l’eccezione, e quindi introduce un handler in grado di catturare l’eccezione lanciata dalla funzione “somma”.
Il costrutto catch() (exception handler) può essere usato solo dopo un blocco preceduto dalla keyword try o dopo un altro blocco catch().
Il blocco try deve contenere le istruzioni che possono generare l’eccezione.
catch() ha tra parentesi una dichiarazione simile a quella degli argomenti di una funzione, può infatti catturate solo eccezioni del tipo specificato. Nel caso specifico l’eccezione è una stringa di caratteri.
Mostra codice

Gestione delle eccezioni

Se una funzione chiamata nel blocco try lancia un’eccezione:

  • le istruzioni nel blocco try seguenti tale funzione non vengono eseguite;
  • viene eseguito (se esiste) solo il primo handler trovato per il tipo di eccezione lanciata.
Mostra codice

Cattura delle eccezioni

try {
// codice che lancia un'eccezione di tipo E;
}
catch (T) {
// quando arriviamo qui?
}

T è dello stesso tipo di E (o una sua classe base, come vedremo in una prossima lezione).
T ed E sono di tipo puntatore ed 1) vale per il tipo delle variabili puntate.
T è un riferimento ed 1) vale per il tipo a cui T si riferisce.

Gestione delle eccezioni

catch (…) può essere usato per catturare qualunque tipo di eccezione.
Se posto in testa ad una lista di handler fa sì che gli altri handler non vengano mai considerati.
Il compilatore g++ (versione 4.3) segnala errore se catch (…) non è l’ultimo handler della lista.

Rilancio di un’eccezione

Se un handler non è in grado di gestire completamente un’eccezione, può rilanciarla, assumendo che un altro handler possa completarne la gestione

try {
// codice che può lanciare un'eccezione di tipo E
}
catch (E) {
// fa qualcosa
throw; // rilancia l'eccezione originaria
}

Specificazione delle eccezioni

La dichiarazione di una funzione può contenere la lista di eccezioni che possono essere lanciate da tale funzione

short somma (short x, short y)throw (const char*);
Il vantaggio è che la dichiarazione appartiene ad un’interfaccia che è visibile al chiamante.
Nota: se somma lancia un’eccezione diversa da const char*, viene chamata abort() che termina il programma.
Per specificare che una funzione non lancia eccezioni:

int g () throw ();

L’operatore new e le eccezioni

Quando la memoria heap è esaurita, come avviene seguendo il programma in esempio, il programma termina con il messaggio del tipo:

terminate called after throwing an instance of ’std::bad_alloc’
what(): std::bad_alloc
Aborted
Cosa è successo?

Mostra codice

L’operatore new e le eccezioni

Dall’header file <new> :

void* operator new(std::size_t) throw (std::bad_alloc);
void* operator new[](std::size_t) throw (std::bad_alloc);
void operator delete(void*) throw();
void operator delete[](void*) throw();

new può lanciare un’eccezione di tipo std::bad_alloc invce delete non lancia alcuna eccezione.

L’operatore new e le eccezioni

Nel seguente esempio, l’eccezione lanciata da new viene gestita da un handler che visualizza un messaggio di errore.
il ciclo while viene terminato.
L’output del programma è il seguente:
Memoria insufficiente
Il programma può continuare…

Mostra codice

Un’altra versione dell’operatore new

In libreria sono definite diverse versioni dell’operatore new. Dall’header file :

struct nothrow_t { };
extern const nothrow_t nothrow;

void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();

La versione “nothrow” di new sopra riportata non lancia alcuna eccezione e restituisce un puntatore NULL se non riesce ad allocare la memoria richiesta.

L’operatore new e nothrow

La versione “nothrow” di new non lancia un’eccezione ma restituisce un puntatore nullo. Pertanto il valore ritornato da new può essere interrogato, in modo da intraprendere una appropriata azione nel caso di mancata allocazione.
Tale situazione è esemplificata nel prossimo codice di esempio. L’output del programma è il seguente:

Memoria insufficiente
Il programma può continuare…

Mostra codice

Esercizio

Scrivere una funzione che effettui la divisione di due numeri reali forniti in ingresso e lanci una eccezione in caso di divisione per zero.
Scrivere un programma chiamante che invochi la funzione e preveda un handler per la gestione dell’eccezione.

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion