Per me la gestione delle eccezioni può essere eccezionale se tutto è conforme a RAII e stai riservando eccezioni a percorsi davvero eccezionali (ad esempio: un file corrotto in fase di lettura) e non hai mai bisogno di oltrepassare i limiti del modulo e tutto il tuo team è a bordo con loro ......
EH a costo zero
La maggior parte dei compilatori in questi giorni implementa la gestione delle eccezioni a costo zero che può renderlo ancora più economico della ramificazione manuale delle condizioni di errore nei percorsi di esecuzione regolari, sebbene in cambio renda i percorsi eccezionali enormemente costosi (c'è anche un po 'di codice gonfiato da esso, anche se probabilmente non di più che se hai gestito a fondo ogni possibile errore manualmente). Il fatto che lanciare sia enormemente costoso non dovrebbe importare se le eccezioni vengono utilizzate nel modo in cui sono intese in C ++ (per circostanze veramente eccezionali che non dovrebbero accadere in condizioni normali).
Inversione di effetto collaterale
Detto questo, rendere tutto conforme a RAII è molto più facile a dirsi che a farsi. È facile per le risorse locali archiviate in una funzione avvolgerle in oggetti C ++ con distruttori che si ripuliscono, ma non è così facile scrivere la logica di annullamento / rollback per ogni singolo effetto collaterale che potrebbe verificarsi nell'intero software. Considera solo quanto sia difficile scrivere un contenitore come quello std::vector
che è la sequenza di accesso casuale più semplice per essere perfettamente sicuro dalle eccezioni. Ora moltiplica quella difficoltà tra tutte le strutture di dati di un intero software su larga scala.
Come esempio di base, considera un'eccezione riscontrata nel processo di inserimento di elementi in un grafico di scena. Per ripristinare correttamente da tale eccezione, potrebbe essere necessario annullare tali modifiche nel grafico della scena e ripristinare il sistema in uno stato come se l'operazione non si fosse mai verificata. Ciò significa rimuovere automaticamente i bambini inseriti nel grafico della scena quando incontrano un'eccezione, forse da una guardia dell'ambito.
Farlo accuratamente in un software che causa molti effetti collaterali e gestisce molti stati persistenti è molto più facile a dirsi che a farsi. Spesso penso che la soluzione pragmatica sia quella di non preoccuparsene nell'interesse di spedire le cose. Con il giusto tipo di base di codice che ha un qualche tipo di sistema di annullamento centrale e forse strutture di dati persistenti e il numero minimo di funzioni che causano effetti collaterali, potresti essere in grado di raggiungere la sicurezza delle eccezioni su tutta la linea ... ma è un sacco di cose di cui hai bisogno se tutto ciò che farai è cercare di rendere il tuo codice sicuro dalle eccezioni ovunque.
C'è qualcosa di praticamente sbagliato lì perché l'inversione degli effetti collaterali concettualmente è un problema difficile, ma è resa più difficile in C ++. A volte è effettivamente più facile invertire gli effetti collaterali in C in risposta a un codice di errore piuttosto che farlo in risposta a un'eccezione in C ++. Uno dei motivi è che qualsiasi punto dato di qualsiasi funzione potrebbe potenzialmente throw
in C ++. Se stai provando a scrivere un contenitore generico che lavora con il tipo T
, non puoi nemmeno prevedere dove si trovano quei punti di uscita, dal momento che tutto ciò che riguarda T
potrebbe lanciare. Anche il confronto tra T
loro per l'uguaglianza potrebbe lanciare. Il tuo codice deve gestire ogni possibilità e il numero di possibilità si moltiplica esponenzialmente in C ++ mentre in C sono molto meno numerose.
Mancanza di standard
Un altro problema di gestione delle eccezioni è la mancanza di standard centrali. Ci sono alcuni come si spera che tutti concordino sul fatto che i distruttori non dovrebbero mai lanciare dal momento che i rollback non dovrebbero mai fallire, ma questo riguarda solo un caso patologico che in genere si bloccherebbe il software se si violasse la regola.
Dovrebbero esserci degli standard più sensati come non lanciare mai da un operatore di confronto (tutte le funzioni che sono logicamente immutabili non dovrebbero mai essere lanciate), mai lanciare da un gestore di movimento, ecc. Tali garanzie renderebbero molto più facile scrivere un codice sicuro per le eccezioni, ma non abbiamo tali garanzie. Dobbiamo seguire la regola che tutto potrebbe lanciare - se non ora, quindi probabilmente in futuro.
Peggio ancora, le persone di diversa lingua guardano alle eccezioni in modo molto diverso. In Python, in realtà hanno questa filosofia "salta prima di guardare" che implica l'attivazione di eccezioni in percorsi di esecuzione regolari! Quando poi ottieni tali sviluppatori che scrivono codice C ++, potresti vedere le eccezioni che vengono lanciate a destra e a sinistra per cose che non sono davvero eccezionali, e gestirle tutte può essere un incubo se stai cercando di scrivere codice generico, ad es.
Gestione degli errori
La gestione degli errori ha lo svantaggio di dover verificare ogni possibile errore che potrebbe verificarsi. Ma ha un vantaggio che a volte è possibile verificare gli errori leggermente in ritardo, come glGetError
, per ridurre significativamente i punti di uscita in una funzione e renderli espliciti. A volte puoi continuare a eseguire il codice un po 'più a lungo prima di controllare, propagare e recuperare da un errore, e talvolta può essere davvero più semplice della gestione delle eccezioni (specialmente con l'inversione degli effetti collaterali). Ma potresti anche non preoccuparti degli errori tanto in un gioco, magari semplicemente chiudere il gioco con un messaggio se incontri file corrotti, errori di memoria insufficiente, ecc.
Come riguarda lo sviluppo di una libreria utilizzata attraverso più progetti.
Una brutta parte delle eccezioni nei contesti DLL è che non è possibile gettare in sicurezza eccezioni da un modulo a un altro a meno che non si possa garantire che siano costruite dallo stesso compilatore, utilizzare le stesse impostazioni, ecc. Quindi, se si scrive come un'architettura di plugin destinato a persone che possono essere utilizzate da tutti i tipi di compilatori e forse anche da lingue diverse come Lua, C, C #, Java, ecc., spesso le eccezioni iniziano a diventare una seccatura poiché devi ingoiare ogni eccezione e tradurle in errore codici comunque dappertutto.
Cosa fa per l'utilizzo di librerie di terze parti.
Se sono dylibs, non possono generare eccezioni in modo sicuro per i motivi sopra menzionati. Dovrebbero usare i codici di errore, il che significa anche che, usando la libreria, dovresti controllare costantemente i codici di errore. Potrebbero avvolgere il loro dylib con una lib wrapper C ++ collegata staticamente che ti costruisci che traduce i codici di errore dal dylib in eccezioni generate (fondamentalmente gettando dal tuo binario nel tuo binario). In generale, penso che la maggior parte delle librerie di terze parti non dovrebbe nemmeno preoccuparsi e attenersi ai codici di errore se usano dylibs / librerie condivise.
In che modo influisce sui test unitari, i test di integrazione, ecc.?
In genere non incontro team che testano così tanto percorsi eccezionali / di errore del loro codice. Lo facevo e in realtà avevo un codice che poteva recuperare correttamente bad_alloc
perché volevo rendere il mio codice ultra-robusto, ma non aiuta davvero quando sono l'unico a farlo in una squadra. Alla fine ho smesso di disturbare. Immagino per un software mission-critical che interi team possano verificare per assicurarsi che il loro codice recuperi in modo solido da eccezioni ed errori in tutti i casi possibili, ma è un sacco di tempo da investire. Il tempo ha senso nei software mission-critical ma forse non è un gioco.
Amore odio
Le eccezioni sono anche il mio tipo di funzionalità di amore / odio del C ++ - una delle caratteristiche più profondamente incisive del linguaggio che rende RAII più un requisito che una convenienza, poiché cambia il modo in cui devi scrivere il codice sottosopra, diciamo , C per gestire il fatto che una determinata riga di codice potrebbe essere un punto di uscita implicito per una funzione. A volte vorrei quasi che la lingua non avesse eccezioni. Ma ci sono momenti in cui trovo davvero utili le eccezioni, ma per me sono un fattore determinante per la scelta di scrivere un pezzo di codice in C o C ++.
Sarebbero esponenzialmente più utili per me se il comitato standard si concentrasse fortemente sulla definizione di standard ABI che consentano di trasferire in modo sicuro le eccezioni tra i moduli, almeno dal codice C ++ al codice C ++. Sarebbe fantastico se potessi tranquillamente passare attraverso le lingue, anche se è un po 'un sogno irrealizzabile. L'altra cosa che renderebbe esponenzialmente le eccezioni più utili per me è se le noexcept
funzioni causassero effettivamente un errore del compilatore se provassero a invocare qualsiasi funzionalità che potesse lanciare invece di chiamare semplicemente std::terminate
incontrando un'eccezione. È quasi inutile (potrebbe anche chiamarlo icantexcept
invece di noexcept
). Sarebbe molto più semplice garantire che tutti i distruttori siano sicuri per le eccezioni, ad esempio senoexcept
in realtà ha causato un errore del compilatore se il distruttore ha fatto qualcosa che poteva lanciare. Se è troppo costoso per determinarlo in modo accurato in fase di compilazione, quindi rendilo così così le noexcept
funzioni (che dovrebbero essere implicitamente tutti i distruttori) possono solo chiamare noexcept
funzioni / operatori e rendere un errore del compilatore da lanciare da uno qualsiasi di essi.