Procedure consigliate C++ moderne per le eccezioni e la gestione degli errori

Nella maggior parte degli scenari C++ moderni, il modo migliore per segnalare e gestire sia gli errori di logica che gli errori di runtime consiste nell'usare le eccezioni. È particolarmente vero quando lo stack potrebbe contenere diverse chiamate di funzione tra la funzione che rileva l'errore e la funzione che ha il contesto per gestire l'errore. Le eccezioni forniscono un modo formale e ben definito per il codice che rileva gli errori per passare le informazioni allo stack di chiamate.

Usare eccezioni per codice eccezionale

Gli errori del programma sono spesso suddivisi in due categorie:

  • Errori logici causati da errori di programmazione. Ad esempio, un errore "indice non compreso nell'intervallo".
  • Errori di runtime che esulano dal controllo del programmatore. Ad esempio, un errore "servizio di rete non disponibile".

Nella programmazione in stile C e in COM, la segnalazione errori viene gestita restituendo un valore che rappresenta un codice di errore o un codice di stato per una determinata funzione oppure impostando una variabile globale che il chiamante può eventualmente recuperare dopo ogni chiamata di funzione per verificare se sono stati segnalati errori. Ad esempio, la programmazione COM usa il HRESULT valore restituito per comunicare gli errori al chiamante. E l'API Win32 ha la GetLastError funzione per recuperare l'ultimo errore segnalato dallo stack di chiamate. In entrambi questi casi, spetta al chiamante riconoscere il codice e rispondere in modo appropriato. Se il chiamante non gestisce in modo esplicito il codice di errore, il programma potrebbe arrestarsi in modo anomalo senza alcun avviso. In alternativa, potrebbe continuare a essere eseguito usando dati non corretti e produrre risultati non corretti.

Le eccezioni sono preferite in C++ moderno per i motivi seguenti:

  • Un'eccezione forza la chiamata di codice per riconoscere una condizione di errore e 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 dell'eccezione. Non devono coordinarsi con altri livelli.
  • Il meccanismo di rimozione dello stack di eccezioni elimina definitivamente tutti gli oggetti nell'ambito dopo la generazione di un'eccezione, in base a regole ben definite.
  • Un'eccezione abilita una separazione pulita tra il codice che rileva l'errore e il codice che gestisce l'errore.

L'esempio semplificato seguente illustra la sintassi necessaria per generare e intercettare le eccezioni in C++:

#include <stdexcept>
#include <limits>
#include <iostream>

using namespace std;

void MyFunc(int 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 nei linguaggi come C# e Java. try Nel blocco, se viene generata un'eccezione, viene intercettata dal primo blocco associato il cui tipo corrisponde a catch quello dell'eccezione. In altre parole, l'esecuzione passa dall'istruzione throw all'istruzione catch . Se non viene trovato alcun blocco catch utilizzabile, std::terminate viene richiamato e il programma viene chiuso. In C++, qualsiasi tipo può essere generato; È tuttavia consigliabile generare un tipo che deriva direttamente o indirettamente da std::exception. Nell'esempio precedente il tipo di eccezione , invalid_argumentè definito nella libreria standard nel <stdexcept> file di intestazione. C++ non fornisce o richiede un finally blocco per assicurarsi che tutte le risorse vengano rilasciate se viene generata un'eccezione. L'acquisizione delle risorse è l'idioma di inizializzazione (RAII), che usa puntatori intelligenti, fornisce la funzionalità necessaria per la pulizia delle risorse. Per altre informazioni, vedere Procedura: Progettare per la sicurezza delle eccezioni. Per informazioni sul meccanismo di rimozione dello stack C++, vedere Eccezioni e rimozione dello stack.

Linee guida di base

Una gestione degli errori affidabile è complessa in qualsiasi linguaggio di programmazione. Sebbene le eccezioni forniscano diverse funzionalità che supportano una corretta gestione degli errori, non possono eseguire tutte le operazioni. Per sfruttare i vantaggi del meccanismo di eccezione, tenere presenti le eccezioni durante la progettazione del codice.

  • Usare asserzioni per verificare la presenza di condizioni che devono essere sempre true o sempre false. Usare le eccezioni per verificare la presenza di errori che possono verificarsi, ad esempio errori nella convalida dell'input sui parametri delle funzioni pubbliche. Per altre informazioni, vedere la sezione Eccezioni e asserzioni .
  • Usare le eccezioni quando il codice che gestisce l'errore è separato dal codice che rileva l'errore da una o più chiamate di funzione intermedie. Valutare se usare i codici di errore invece nei cicli critici per le prestazioni, quando il codice che gestisce l'errore è strettamente associato al codice che lo rileva.
  • Per ogni funzione che potrebbe generare o propagare un'eccezione, fornire una delle tre garanzie di eccezione: la garanzia assoluta, la garanzia di base o la garanzia nothrow (noexcept). Per altre informazioni, vedere Procedura: Progettare per la sicurezza delle eccezioni.
  • Generare eccezioni per valore, intercettarle in base al riferimento. Non intercettare ciò che non puoi gestire.
  • Non usare specifiche di eccezione, deprecate in C++11. Per altre informazioni, vedere la sezione Specifiche e noexcept specifiche delle eccezioni.
  • Quando si applicano, usare i tipi di eccezione della libreria standard. Derivare tipi di eccezione personalizzati dalla exception gerarchia di classi .
  • Non consentire alle eccezioni di eseguire l'escape dai distruttori o dalle funzioni di deallocazione della memoria.

Eccezioni e prestazioni

Il meccanismo di eccezione ha un costo minimo delle prestazioni se non viene generata alcuna eccezione. Se viene generata un'eccezione, il costo dell'attraversamento e della rimozione dello stack è approssimativamente paragonabile al costo di una chiamata di funzione. Altre strutture di dati sono necessarie per tenere traccia dello stack di chiamate dopo l'immissione di un try blocco e sono necessarie altre istruzioni per rimuovere lo stack se viene generata un'eccezione. Nella maggior parte degli scenari, tuttavia, il costo delle prestazioni e del footprint della memoria non è significativo. È probabile che l'effetto negativo delle eccezioni sulle prestazioni sia significativo solo nei sistemi con vincoli di memoria. In alternativa, nei cicli critici per le prestazioni, in cui è probabile che si verifichi regolarmente un errore e che sia presente un accoppiamento stretto tra il codice per gestirlo e il codice che lo segnala. In ogni caso, è impossibile conoscere il costo effettivo delle eccezioni senza profilatura e misurazione. Anche in questi rari casi in cui il costo è significativo, è possibile valutarlo rispetto all'aumento della correttezza, alla manutenibilità più semplice e ad altri vantaggi offerti da criteri di eccezione ben progettati.

Eccezioni e asserzioni

Le eccezioni e le asserzioni sono due meccanismi distinti per rilevare gli errori di runtime in un programma. Usare assert istruzioni per verificare le condizioni durante lo sviluppo che devono essere sempre true o sempre false se tutto il codice è corretto. Non c'è alcun punto nella gestione di un errore di questo tipo usando un'eccezione, perché l'errore indica che è necessario correggere un elemento nel codice. Non rappresenta una condizione da cui il programma deve eseguire il ripristino in fase di esecuzione. Un'istruzione arresta assert l'esecuzione nell'istruzione in modo da poter controllare lo stato del programma nel debugger. Un'eccezione continua l'esecuzione dal primo gestore catch appropriato. Usare le eccezioni per controllare le condizioni di errore che possono verificarsi in fase di esecuzione anche se il codice è corretto, ad esempio "file non trovato" o "memoria insufficiente". Le eccezioni possono gestire queste condizioni, anche se il ripristino restituisce solo un messaggio a un log e termina il programma. Controllare sempre gli argomenti per le funzioni pubbliche usando le eccezioni. Anche se la funzione è senza errori, potrebbe non essere disponibile il controllo completo sugli argomenti che un utente potrebbe passare.

Eccezioni C++ e eccezioni di Windows edizione Standard H

I programmi C e C++ possono usare il meccanismo di gestione delle eccezioni strutturate (edizione Standard H) nel sistema operativo Windows. I concetti in edizione Standard H sono simili a quelli delle eccezioni C++, ad eccezione del fatto che edizione Standard H usa __tryi costrutti , __excepte __finally anziché try e catch. Nel compilatore Microsoft C++ (MSVC), le eccezioni C++ vengono implementate per edizione Standard H. Tuttavia, quando si scrive codice C++, usare la sintassi delle eccezioni C++.

Per altre informazioni su edizione Standard H, vedere Structured Exception Handling (C/C++).

Specifiche delle eccezioni e noexcept

Le specifiche di eccezione sono state introdotte in C++ come modo per specificare le eccezioni che una funzione potrebbe generare. Tuttavia, le specifiche delle eccezioni si sono rivelate problematiche nella pratica e sono deprecate nello standard di bozza C++11. È consigliabile non usare throw specifiche di eccezione ad eccezione throw()di , che indica che la funzione non consente l'escape delle eccezioni. Se è necessario usare le specifiche di eccezione del formato throw( type-name )deprecato, il supporto MSVC è limitato. Per altre informazioni, vedere Specifiche delle eccezioni (throw).For more information, see Exception Specifications (throw). L'identificatore noexcept viene introdotto in C++11 come alternativa preferita a throw().

Vedi anche

Procedura: Interfaccia tra codice eccezionale e non eccezionale
Informazioni di riferimento sul linguaggio C++
Libreria standard C++