Condividi tramite


Gestione di errori ed eccezioni (C++ moderno)

Nel linguaggio C++ moderno, nella maggior parte degli scenari, il modo preferito per segnalare e gestire gli errori logici e gli errori di runtime è di utilizzare le eccezioni. Questo è particolarmente vero quando lo stack può contenere diverse chiamate di funzione tra la funzione che rileva l'errore e la funzione con il contesto per sapere come gestirlo. Le eccezioni forniscono un metodo convenzionale e ben definito per il codice che rileva errori di passare le informazioni allo stack di chiamate.

Gli errori di programma sono in genere suddivisi in due categorie: gli errori logici causati da errori di programmazione, ad esempio "un errore di indice non compreso nell'intervallo", e gli errori di runtime che non dipendono dal programmatore, ad esempio un errore causato da un "servizio di rete non disponibile". Nella programmazione di tipo C e in COM, la segnalazione degli errori è gestita restituendo un valore che rappresenta un codice di errore o un codice di stato per una particolare funzione, o impostando una variabile globale che il chiamante può recuperare dopo ogni chiamata di funzione per verificare se sono stati segnalati tali errori. Ad esempio, la programmazione COM utilizza il valore restituito HRESULT per comunicare gli errori al chiamante e l'API Win32 ha la funzione GetLastError per recuperare l'ultimo errore che è stato segnalato dallo stack di chiamata. In entrambi casi, è responsabilità del chiamante di riconoscere il codice e di rispondervi in modo appropriato. Se il chiamante non gestisce in modo esplicito il codice di errore, il programma potrebbe arrestarsi in modo anomalo senza avviso o continuare l'esecuzione con i dati errati e produrre risultati non corretti.

Le eccezioni sono preferibili nel linguaggio C++ moderno per i motivi seguenti:

  • Un'eccezione forza il codice chiamante affinché riconosca una condizione di errore e possa gestirla. Le eccezioni non gestite arrestano l'esecuzione del programma.

  • Un'eccezione passa al punto nello stack di chiamate che può gestire l'errore. Le funzioni intermedie possono consentire la propagazione delle eccezioni. Non devono necessariamente coordinarsi con altri livelli.

  • Il meccanismo di rimozione dello stack di eccezione elimina tutti gli oggetti nell'ambito in base alle regole ben definite una volta generata un'eccezione.

  • Un'eccezione consente una netta separazione tra codice che rileva l'errore e il codice che lo gestisce.

Nell'esempio semplificato che segue viene illustrata la sintassi necessaria per generare e rilevare eccezioni in C++.

 
#include <stdexcept>
#include <limits>
#include <iostream>
 
using namespace std;
class MyClass
{
public:
   void MyFunc(char c)
   {
      if(c < numeric_limits<char>::max())
         throw invalid_argument("MyFunc argument too large.");
      //...
   }
};

int main()
{
   try
   {
      MyFunc(256); //cause an exception to throw
   }
 
   catch(invalid_argument& e)
   {
      cerr << e.what() << endl;
      return -1;
   }
   //...
   return 0;
}

Le eccezioni in C++ sono simili a quelle presenti nei linguaggi come C# e Java. Nel blocco try, se viene generata un'eccezione, questa verrà rilevata dal primo blocco catch associato il cui tipo corrisponde a quello dell'eccezione. Il altre parole, l'esecuzione passa dall'istruzione throw all'istruzione catch. Se non viene rilevato alcun blocco catch, std::terminate viene richiamato e il programma viene terminato. In C++, è possibile generare qualsiasi tipo; tuttavia, si consiglia di generare un tipo che deriva direttamente o indirettamente da std::exception. Nell'esempio precedente, il tipo di eccezione, invalid_argument, viene definito nella libreria standard nel file di intestazione <stdexcept>. C++ non fornisce e non richiede un blocco finally per verificare che tutte le risorse vengano rilasciate qualora venga generata un'eccezione. Il modello RAII (Resource Acquisition Is Initialization), che utilizza i puntatori intelligenti, fornisce la funzionalità richiesta per la pulizia delle risorse. Per ulteriori informazioni, vedere Procedura: progettare la sicurezza dell'eccezione. Per informazioni sul meccanismo di rimozione dello stack C++, vedere Eccezioni e rimozione dallo stack in C++.

Linee guida di base

La gestione degli errori affidabile è impegnativa in qualsiasi linguaggio di programmazione. Sebbene le eccezioni forniscano numerose funzionalità che supportano una gestione efficace degli errori, non possono eseguire tutte le operazioni automaticamente. Per comprendere i vantaggi del meccanismo delle eccezioni, tenere presenti le eccezioni quando si progetta il codice.

  • Utilizzare le asserzioni per il controllo degli errori che non devono verificarsi mai. Utilizzare le eccezioni per verificare la presenza di errori che potrebbero verificarsi, ad esempio, errori nella convalida di input nei parametri delle funzioni pubbliche. Per ulteriori informazioni, vedere la sezione denominata Eccezione e asserzioni.

  • Utilizzare le eccezioni quando il codice che gestisce l'errore potrebbe essere separato dal codice che rileva l'errore da una o più chiamate alle funzioni. Considerare se utilizzare piuttosto codici di errore nei cicli con particolari requisiti di prestazione quando il codice che gestisce l'errore è strettamente collegato al codice che lo rileva. Per ulteriori informazioni sulle situazioni in cui non è consigliabile utilizzare le eccezioni, vedere When Not to Use Exceptions.

  • Per ogni funzione che potrebbe generare o propagare un'eccezione, specificare una delle tre garanzie di eccezione: la garanzia forte, la garanzia di base, o la garanzia nothrow (noexcept). Per ulteriori informazioni, vedere Procedura: progettare la sicurezza dell'eccezione.

  • Generare eccezioni in base al valore, acquisirle in base al riferimento. Non rilevare ciò che non è possibile gestire. Per ulteriori informazioni, vedere Linee guida per la generazione e intercettare le eccezioni (C++).

  • Non utilizzare le specifiche di eccezione, deprecate in C++11. Per ulteriori informazioni, vedere la sezione denominata Specifiche eccezione e noexcept.

  • Utilizzare i tipi di eccezioni della libreria standard quando necessario. Derivare i tipi di eccezioni personalizzate dalla gerarchia Classe di eccezione. Per ulteriori informazioni, vedere Procedura: Utilizzare gli oggetti eccezione standard di libreria.

  • Non consentire le eccezioni per evitare le funzioni dei distruttori o di deallocazione della memoria.

Eccezioni e prestazioni

Il meccanismo di eccezione ha un costo minimo sulle prestazioni se non viene generata alcuna eccezione. Se viene generata un'eccezione, il costo dell'attraversamento e della rimozione è approssimativamente paragonabile al costo di una chiamata di funzione. Le strutture di dati aggiuntive sono necessarie per tenere traccia dello stack di chiamate dopo l'immissione di un blocco try. Istruzioni aggiuntive sono invece richieste per rimuovere lo stack se viene generata un'eccezione. Tuttavia, nella maggior parte degli scenari, il costo delle prestazioni e del footprint di memoria non è significativo. L'effetto negativo delle eccezioni sulle prestazioni potrebbe essere significativo solo sui sistemi dotati di una memoria molto limitata o nei cicli in cui le prestazioni sono fondamentali perché un errore potrebbe verificarsi regolarmente e il codice per gestirlo è strettamente collegato al codice che lo segnala. Tuttavia, è impossibile individuare il costo effettivo delle eccezioni senza la profilatura e la misurazione. Anche nei rari casi in cui il costo è significativo, è possibile valutarlo rispetto alla maggiore precisione, alla manutenibilità più semplice e ad altri vantaggi forniti dai criteri di eccezione progettati correttamente.

Eccezioni e asserzioni

Le eccezioni e le asserzioni sono due distinti meccanismi per rilevare errori di runtime in un programma. Utilizzare le asserzioni per verificare le condizioni durante lo sviluppo che non dovrebbero mai essere true se il codice è corretto. Non è possibile gestire tale errore utilizzando un'eccezione perché l'errore indica che qualcosa nel codice deve essere corretto e non rappresenta una condizione da cui il programma deve recuperare in fase di esecuzione. Un'asserzione arresta l'esecuzione in corrispondenza di un'istruzione per consentire di verificare lo stato del programma nel debugger; un'eccezione continua l'esecuzione a partire dal primo gestore catch appropriato. Utilizzare le eccezioni per controllare le condizioni di errore che possono verificarsi in fase di esecuzione se il codice è corretto, ad esempio, "file non trovato" o "memoria insufficiente". È possibile recuperare da queste condizioni, anche se il recupero restituisce semplicemente un messaggio a un registro e termina il programma. Verificare sempre gli argomenti rispetto alle funzioni pubbliche utilizzando le eccezioni. Anche se la funzione è senza errori, è possibile che non si abbia il controllo completo sugli argomenti che un utente può passare.

Eccezioni C++ ed eccezioni SEH in Windows

I programmi C e C++ possono utilizzare il meccanismo di gestione delle eccezioni strutturata (SEH) nel sistema operativo Windows. I concetti in SEH sono simili a quelli nelle eccezioni in C++, eccetto per il fatto che SEH utilizza i costrutti __try, __except e __finally anziché try e catch. In Visual C++ le eccezioni sono implementate per SEH. Tuttavia, quando si scrive il codice C++, utilizzare la sintassi di eccezione C++.

Per ulteriori informazioni su SEH, vedere Gestione strutturata delle eccezioni (C/C++).

Specifiche di eccezione e noexcept

Le specifiche di eccezione sono introdotte in C++ come modo per specificare le eccezioni che una funzione potrebbe generare. Tuttavia, le specifiche di eccezione sono risultate problematiche nella pratica e sono deprecate nello standard della bozza C++11. Si consiglia di non utilizzare le specifiche di eccezione tranne throw(), che indica che l'eccezione non consente eccezioni per l'escape. Per utilizzare le specifiche di eccezione del tipo throw(type), tenere presente che Visual C++ parte dallo standard in qualche modo. Per ulteriori informazioni, vedere Specifiche di eccezioni. L'identificatore noexcept viene introdotto in C++11 come l'alternativa consigliata a throw().

Vedere anche

Concetti

Procedura: interfaccia tra codice eccezionale e non eccezionale

Altre risorse

C++ (C++ moderno)

Riferimenti del linguaggio C++

Riferimento per la libreria standard C++