Eccezioni nelle DLL dei giochi C ++? Pro e contro


8

Quali sono i pro e i contro dell'utilizzo di Exceptions in C ++ in relazione allo sviluppo del gioco.

La guida di stile di Google afferma che non usano le eccezioni per una serie di motivi. Le stesse ragioni sono pertinenti allo sviluppo del gioco?

Non utilizziamo eccezioni C ++ ... - google-styleguide.googlecode.com

Alcuni problemi a cui pensare.

  • Come riguarda lo sviluppo di una libreria utilizzata attraverso più progetti.
  • In che modo influisce sui test unitari, i test di integrazione, ecc.?
  • Cosa fa per l'utilizzo di librerie di terze parti.

3
Stranamente nessuno ha avuto alcun vantaggio
David Young,

Risposte:


7

Ci sono anche alcune brutte conseguenze sull'uso delle eccezioni in C ++ se non si conoscono tutti i dettagli di come funzionano. Poiché molti giochi sono scritti in C ++, questo può essere un fattore.

Ad esempio, in C ++ il lancio di un'eccezione in un distruttore può avere gravi conseguenze. Può causare tutti i tipi di comportamento indefinito e arresti anomali, perché puoi rimanere intrappolato in una situazione in cui hai più di un'eccezione attiva in volo. (Vedi l'articolo 8 di C ++ effettivo per maggiori informazioni su questo.)


4
+1 per C ++ efficace. Leggendo la terza edizione mentre parliamo. L'eccezione va bene per me, purché tu sia molto severo su dove li usi, dove li catturi e puoi garantire che qualsiasi codice di terze parti faccia lo stesso.
Anthony,

9

Joel parla delle eccezioni del software.

Vista interessante non attualmente in nessuna delle risposte,

Il ragionamento è che considero le eccezioni non migliori di "goto", considerate dannose sin dagli anni '60, in quanto creano un brusco salto da un punto di codice a un altro. In realtà sono significativamente peggiori di quelli di goto:

  1. Sono invisibili nel codice sorgente. Osservando un blocco di codice, incluse le funzioni che possono o meno generare eccezioni, non c'è modo di vedere quali eccezioni potrebbero essere generate e da dove. Ciò significa che anche un'attenta ispezione del codice non rivela potenziali bug.

  2. Creano troppi possibili punti di uscita per una funzione. Per scrivere il codice corretto, devi davvero pensare a ogni possibile percorso del codice attraverso la tua funzione. Ogni volta che chiami una funzione che può sollevare un'eccezione e non rilevarla sul posto, crei opportunità per bug a sorpresa causati da funzioni che si sono interrotte bruscamente, lasciando i dati in uno stato incoerente o altri percorsi di codice che non hai Pensa a.

http://www.joelonsoftware.com/items/2003/10/13.html


Alleluia ... Grande riferimento. ho avuto un'avversione con eccezioni proprio per questo motivo, ma a quanto pare è il modo preferito di fare le cose al giorno d'oggi. Fantastico vedere un bell'articolo che offre un'altra visione
Rospo,

1
+1 perché le eccezioni hanno un rischio troppo elevato di fallire in ritardo, ovvero quando il gioco è già stato spedito.
martinpi,

Concordo pienamente con il punto numero 2. Non combatterò contro le lingue che usano eccezioni, ma non le considero neanche il modo "giusto" per gestire le condizioni di errore.
Kylotan,

5

Le eccezioni non sono supportate e quindi altamente scoraggiate in almeno un moderno ambiente di sviluppo console.

La maggior parte degli sviluppatori di console che conosco preferiscono non usarli comunque a causa del sovraccarico aggiunto nell'eseguibile e della filosofia che semplicemente non sono necessari. RTTI è visto allo stesso modo.


Su quale console non è supportata?
David Young,

Sto cercando di essere timido a causa degli NDA, anche se prima è stato rivelato più specificamente su SO.
Dan Olson,

2

Di tutti i progetti a cui ho lavorato nei giochi, nessuno di loro ha usato eccezioni. L'overhead della chiamata di funzione è il motivo principale. Come accennato in precedenza, come RTTI, nella maggior parte degli studi non è argomento di discussione. Non perché la maggior parte dei programmatori proviene da un background Java / accademico, perché francamente, la maggior parte no.
Non influisce davvero sul test unitario, come si afferma semplicemente sulle condizioni. Per le biblioteche, stai meglio senza eccezioni in quanto lo costringerai a tutti coloro che lo usano.
Mentre rende alcune situazioni leggermente più difficili da gestire, ti (anche) ti costringe ad avere una configurazione più controllata, poiché le eccezioni possono portare ad abusi. La tolleranza agli errori è buona, ma non se porta a una codifica sciatta.
Intendiamoci, questo proviene da qualcuno che non ha mai usato la gestione delle eccezioni (va bene, una o due volte con gli strumenti - non l'ha fatto per me, immagino sia quello con cui cresci).


1
Hai ragione, ma sfortunatamente piuttosto che utilizzare una gestione alternativa degli errori, la maggior parte dei programmatori di giochi si limita a tornare in crash o restituire NULL (che probabilmente porterà comunque a un crash). Non abbiamo trovato alcun sostituto decente.
tenpn,

Restituire NULL, o un codice di errore, è una sostituzione perfettamente ragionevole purché si controllino anche i codici di ritorno.
JasonD,

Sì, ma molti programmatori di giochi no. Qual è il mio punto.
tenpn,

1
È un problema del linguaggio C ++ quanto di più - se vedi una riga di codice che dice function();che non sai all'istante se un codice di errore viene ignorato o meno. Questo è presumibilmente il motivo per cui le lingue che ti consentono di ignorare un valore di ritorno cercano di costringerti a preferire le eccezioni.
Kylotan,

1
L'applicazione di argomenti di errore non è più un problema. Il codice moderno che non utilizza eccezioni si basa su una Errorclasse che è leggera e se ignorata si traduce in un'asserzione che viene lanciata. Quindi non può davvero essere ignorato non più di quanto un'eccezione possa essere ignorata.
Samaursa,

1

La cosa più importante per me come sviluppatore di giochi professionali è che le eccezioni devono essere scritte da persone che capiscono come funzionano le eccezioni (che ci sono poche nel settore dei giochi) e che il codice sottostante che le esegue (che è un brutto pasticcio comunque e ucciderà il tuo processore in ordine) doveva essere scritto da persone che fornivano il tuo compilatore / linker. Il problema è che hanno solo una quantità limitata di risorse, quindi si concentrano sulla scrittura di ottimizzazioni e correzioni migliori per il codice che gli sviluppatori di giochi usano ... che di solito non sono eccezioni. Se hai un bug di eccezione sull'hardware moderno con i nuovi compilatori, chi chiamerai? il tuo esperto di eccezioni che pensa che tutto vada bene con il codice o il venditore che dice, sì, presto, noi "

Non c'è nulla di intrinsecamente sbagliato nelle eccezioni, solo che non sono buone perché la realtà ostacola la teoria.


0

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::vectorche è 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 throwin 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 Tpotrebbe lanciare. Anche il confronto tra Tloro 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_allocperché 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 noexceptfunzioni causassero effettivamente un errore del compilatore se provassero a invocare qualsiasi funzionalità che potesse lanciare invece di chiamare semplicemente std::terminateincontrando un'eccezione. È quasi inutile (potrebbe anche chiamarlo icantexceptinvece di noexcept). Sarebbe molto più semplice garantire che tutti i distruttori siano sicuri per le eccezioni, ad esempio senoexceptin 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 noexceptfunzioni (che dovrebbero essere implicitamente tutti i distruttori) possono solo chiamare noexceptfunzioni / operatori e rendere un errore del compilatore da lanciare da uno qualsiasi di essi.


-2

A mio avviso, ci sono due motivi principali per cui non è tradizionalmente utilizzato nello sviluppo di giochi:

  • La maggior parte dei programmatori di giochi sono laureati, che hanno imparato su Java e Haskell, o programmatori C, che non hanno alcuna esperienza di eccezioni. Quindi hanno abbastanza paura di loro e non sanno come usarli correttamente.
  • Lo stack che si svolge quando viene generata un'eccezione ha implicazioni sulle prestazioni (anche se questo è un tipo di saggezza ricevuta e alcuni riferimenti sarebbero grandi).

3
I programmatori Java non sanno come usare le eccezioni? Java si basa sulla gestione delle eccezioni. Anche i programmatori Java più elementari comprendono i blocchi Try, Catch, Infine. Non puoi davvero programmare in Java senza lanciare Eccezioni o gestirle.
David Young,

4
È più che semplicemente impilare svolgendo. Le eccezioni aggiungono sovraccarico a ogni chiamata di funzione. codeproject.com/KB/cpp/exceptionhandler.aspx
Jonathan Fischoff

@David Young Sto parlando di più sulle garanzie forti / deboli e sul lato più complesso dell'uso delle eccezioni, in cui i laureati potrebbero non essere esperti. A complemento di ciò, molte università sono molto più interessate a come strutturi il tuo OOP così porre meno enfasi sull'imparare a usare correttamente le eccezioni Java. Ma hai ragione, non ero chiaro.
tenpn,

Inoltre, forse "paura" è la parola sbagliata. :)
tenpn

3
Direi che sono spesso l'opposto di "paura" - se non hai una regola rigorosa "nessuna eccezione, nessuna eccezione", i più recenti laureati Java finiranno per lanciare eccezioni più spesso del ritorno, proprio come i programmatori C principianti usa goto e break piuttosto che scrivere invarianti a ciclo buono.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.