In C ++ quali sono i vantaggi dell'utilizzo delle eccezioni e try / catch invece di restituire un codice di errore?

StackOverflow https://stackoverflow.com/questions/196522

Domanda

Ho programmato C e C ++ per molto tempo e finora non ho mai usato eccezioni e try / catch. Quali sono i vantaggi di usare questo invece di avere semplicemente funzioni che restituiscono codici di errore?

È stato utile?

Soluzione

Forse un punto ovvio: uno sviluppatore può ignorare (o non essere consapevole) del tuo stato di restituzione e continuare beato inconsapevolmente che qualcosa non ha funzionato.

Un'eccezione deve essere riconosciuta in qualche modo: non può essere silenziosamente ignorata senza attivamente mettere in atto qualcosa per farlo.

Altri suggerimenti

Il vantaggio delle eccezioni è duplice:

  • Non possono essere ignorati. Devi affrontarli a un certo livello, altrimenti termineranno il tuo programma. Con il codice di errore, è necessario verificarli esplicitamente, altrimenti si perdono.

  • Possono essere ignorati. Se un errore non può essere affrontato a un livello, passerà automaticamente al livello successivo, dove può essere. I codici di errore devono essere passati esplicitamente fino a quando non raggiungono il livello in cui possono essere gestiti.

Il vantaggio è che non è necessario controllare il codice di errore dopo ogni chiamata potenzialmente non riuscita. Affinché ciò funzioni, tuttavia, è necessario combinarlo con le classi RAII in modo che tutto venga automaticamente ripulito mentre lo stack si svolge.

Con messaggi di errore:

int DoSomeThings()
{
    int error = 0;
    HandleA hA;
    error = CreateAObject(&ha);
    if (error)
       goto cleanUpFailedA;

    HandleB hB;
    error = CreateBObjectWithA(hA, &hB);
    if (error)
       goto cleanUpFailedB;

    HandleC hC;
    error = CreateCObjectWithA(hB, &hC);
    if (error)
       goto cleanUpFailedC;

    ...

    cleanUpFailedC:
       DeleteCObject(hC);
    cleanUpFailedB:
       DeleteBObject(hB);
    cleanUpFailedA:
       DeleteAObject(hA);

    return error;
}

Con eccezioni e RAII

void DoSomeThings()
{
    RAIIHandleA hA = CreateAObject();
    RAIIHandleB hB = CreateBObjectWithA(hA);
    RAIIHandleC hC = CreateCObjectWithB(hB);
    ...
}

struct RAIIHandleA
{
    HandleA Handle;
    RAIIHandleA(HandleA handle) : Handle(handle) {}
    ~RAIIHandleA() { DeleteAObject(Handle); }
}
...

A prima vista, la versione RAII / Exceptions sembra più lunga, finché non ti rendi conto che il codice di pulizia deve essere scritto una sola volta (e ci sono modi per semplificarlo). Ma la seconda versione di DoSomeThings è molto più chiara e gestibile.

NON provare a utilizzare le eccezioni in C ++ senza il linguaggio RAII, poiché perderai risorse e memoria. Tutto il tuo cleanup deve essere fatto in distruttori di oggetti allocati in pila.

Mi rendo conto che ci sono altri modi per gestire il codice di errore, ma alla fine sembrano tutti uguali. Se lasci cadere le foto, finisci per ripetere il codice di pulizia.

Un punto per i codici di errore è che rendono ovvio dove le cose possono fallire e come possono fallire. Nel codice sopra, lo scrivi con il presupposto che le cose non falliranno (ma se lo fanno, sarai protetto dai wrapper RAII). Ma finisci per prestare meno attenzione a dove le cose possono andare storte.

La gestione delle eccezioni è utile perché semplifica la separazione del codice di gestione degli errori dal codice scritto per gestire la funzione del programma. Ciò semplifica la lettura e la scrittura del codice.

A parte le altre cose menzionate, non è possibile restituire un codice di errore da un costruttore. Anche i distruttori, ma dovresti evitare di lanciare un'eccezione anche da un distruttore.

  • restituisce un codice di errore quando una condizione di errore è prevista in alcuni casi
  • genera un'eccezione quando una condizione di errore non prevista in ogni caso

nel primo caso il chiamante della funzione deve verificare il codice di errore per l'errore previsto; in quest'ultimo caso l'eccezione può essere gestita da qualsiasi chiamante nello stack (o dal gestore predefinito) come appropriato

Ho scritto un post sul blog su questo ( Le eccezioni fanno per Codice Elegante ), che è stato successivamente pubblicato in Sovraccarico . In realtà l'ho scritto in risposta a qualcosa che Joel ha detto sul podcast StackOverflow!

Ad ogni modo, credo fermamente che le eccezioni siano preferibili ai codici di errore nella maggior parte dei casi. Trovo davvero doloroso usare funzioni che restituiscono codici di errore: devi controllare il codice di errore dopo ogni chiamata, il che può interrompere il flusso del codice di chiamata. Significa anche che non è possibile utilizzare operatori sovraccaricati in quanto non è possibile segnalare l'errore.

La sofferenza di controllare i codici di errore significa che le persone spesso trascurano di farlo, rendendoli così completamente inutili: almeno devi ignorare esplicitamente le eccezioni con un'istruzione catch .

L'uso di distruttori in C ++ e di dispositivi di eliminazione in .NET per garantire che le risorse vengano liberate correttamente in presenza di eccezioni può anche semplificare notevolmente il codice. Per ottenere lo stesso livello di protezione con i codici di errore, devi disporre di molte istruzioni if , molti codici duplicati di pulizia o goto verso un blocco comune di pulizia su la fine di una funzione. Nessuna di queste opzioni è piacevole.

Ecco una buona spiegazione di EAFP (" Più facile da chiedere perdono di Autorizzazione. & Quot;), che a mio avviso si applica anche se si tratta di una pagina Python in Wikipedia. L'uso delle eccezioni porta a uno stile di codifica più naturale, l'IMO - e anche secondo l'opinione di molti altri.

Quando insegnavo C ++, la nostra spiegazione standard era che ti permettevano di evitare intricati scenari di giorni di sole e di pioggia. In altre parole, potresti scrivere una funzione come se tutto funzionasse bene e catturare l'eccezione alla fine.

Senza eccezioni, dovresti ottenere un valore di ritorno da ogni chiamata e assicurarti che sia ancora legittimo.

Un vantaggio correlato, ovviamente, è che non "sprechi". il tuo valore di ritorno su eccezioni (e quindi consentire a metodi che dovrebbero essere nulli di essere nulli) e può anche restituire errori da costruttori e distruttori.

Guida allo stile C ++ di Google ha una fantastica, approfondita analisi dei pro e contro dell'uso delle eccezioni nel codice C ++. Indica anche alcune delle domande più grandi che dovresti porre; cioè intendo distribuire il mio codice ad altri (che potrebbero avere difficoltà a integrarsi con una base di codice abilitata per le eccezioni)?

A volte devi davvero usare un'eccezione per segnalare un caso eccezionale. Ad esempio, se qualcosa va storto in un costruttore e ritieni opportuno notificarlo al chiamante, non hai altra scelta che lanciare un'eccezione.

Un altro esempio: a volte non vi è alcun valore che la funzione può restituire per indicare un errore; qualsiasi valore restituito dalla funzione indica successo.

int divide(int a, int b)
{
    if( b == 0 )
        // then what?  no integer can be used for an error flag!
    else
        return a / b;
}

Il fatto che si debbano riconoscere le eccezioni è corretto ma questo può anche essere implementato usando le strutture di errore. È possibile creare una classe di errore di base che controlla nel suo dtor se è stato chiamato un determinato metodo (ad esempio IsOk). In caso contrario, potresti registrare qualcosa e quindi uscire, o lanciare un'eccezione, o sollevare un'affermazione, ecc ...

Chiamare IsOk sull'oggetto errore senza reagire ad esso, equivarrebbe quindi a scrivere catch (...) {} Entrambe le dichiarazioni mostrerebbero la stessa mancanza di buona volontà del programmatore.

Il trasporto del codice di errore fino al livello corretto è una preoccupazione maggiore. Fondamentalmente dovresti fare in modo che quasi tutti i metodi restituiscano un codice di errore per il solo motivo della propagazione. Ma ancora una volta, una funzione o un metodo dovrebbe sempre essere annotato con le eccezioni che può generare. Quindi in pratica devi avere lo stesso problema, senza un'interfaccia per supportarlo.

Come ha sottolineato @Martin, il lancio di eccezioni obbliga il programmatore a gestire l'errore. Ad esempio, non controllare i codici di ritorno è una delle maggiori fonti di falle di sicurezza nei programmi C. Le eccezioni assicurano di gestire l'errore (si spera) e di fornire un qualche tipo di percorso di recupero per il programma. E se scegli di ignorare un'eccezione anziché introdurre una falla nella sicurezza, il tuo programma si arresta in modo anomalo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top