Scrivi (davvero) un codice sicuro di eccezione? [chiuso]


312

La gestione delle eccezioni (EH) sembra essere lo standard attuale e, cercando nel web, non riesco a trovare nuove idee o metodi che provino a migliorarlo o sostituirlo (beh, esistono alcune varianti, ma niente di nuovo).

Sebbene la maggior parte delle persone sembri ignorarlo o semplicemente accettarlo, EH ha alcuni enormi inconvenienti: le eccezioni sono invisibili al codice e crea molti, molti possibili punti di uscita. Joel sul software ha scritto un articolo al riguardo . Il confronto con la gotomisura perfetta, mi ha fatto ripensare a EH.

Cerco di evitare EH e uso solo valori di ritorno, callback o qualunque cosa si adatti allo scopo. Ma quando devi scrivere un codice affidabile, oggigiorno non puoi semplicemente ignorare EH : inizia con il new, che può generare un'eccezione, invece di restituire solo 0 (come ai vecchi tempi). Ciò rende qualsiasi riga di codice C ++ vulnerabile a un'eccezione. E poi più posti nel codice di base C ++ generano eccezioni ... std lib lo fa e così via.

Sembra di camminare su terreni traballanti .. Quindi, ora siamo costretti a prenderci cura delle eccezioni!

Ma è difficile, è davvero difficile. Devi imparare a scrivere un codice sicuro di eccezione, e anche se hai qualche esperienza con esso, ti sarà comunque richiesto di ricontrollare ogni singola riga di codice per essere sicuro! Oppure inizi a mettere blocchi try / catch ovunque, il che ingombra il codice fino a raggiungere uno stato di illeggibilità.

EH ha sostituito il vecchio approccio deterministico pulito (valori restituiti ...), che presentava solo alcuni inconvenienti ma comprensibili e facilmente risolvibili con un approccio che crea molti possibili punti di uscita nel codice e se si inizia a scrivere codice che rileva eccezioni (ciò che si sono obbligati a farlo ad un certo punto), quindi crea anche una moltitudine di percorsi attraverso il tuo codice (codice nei blocchi catch, pensa a un programma server in cui hai bisogno di strutture di registrazione diverse da std :: cerr ..). EH ha dei vantaggi, ma non è questo il punto.

Le mie attuali domande:

  • Scrivi davvero un codice sicuro?
  • Sei sicuro che il tuo ultimo codice "pronto per la produzione" sia sicuro per le eccezioni?
  • Puoi essere sicuro che lo sia?
  • Conosci e / o effettivamente usi delle alternative che funzionano?

44
"EH ha sostituito il vecchio approccio deterministico pulito (valori di ritorno ..)" Cosa? Le eccezioni sono deterministiche quanto i codici di ritorno. Non c'è nulla di casuale in loro.
rlbond

2
EH è lo standard da molto tempo (anche da Ada!)

12
"EH" è un'abbreviazione consolidata per la gestione delle eccezioni? Non l'ho mai visto prima.
Thomas Padron-McCarthy,

3
Le leggi di Newton sono applicabili oggi per la maggior parte delle cose. È significativo il fatto che per secoli abbiano predetto una vasta gamma di fenomeni.
David Thornley,

4
@frunsi: Heh, scusa per averti confuso! Ma penso che, semmai, un acronimo o un'abbreviazione strano e inspiegabile scoraggerà le persone più che altro.
Thomas Padron-McCarthy,

Risposte:


520

La tua domanda afferma che "Scrivere un codice sicuro per le eccezioni è molto difficile". Prima risponderò alle tue domande e poi risponderò alla domanda nascosta dietro di loro.

Rispondendo alle domande

Scrivi davvero un codice sicuro?

Certo che lo so.

Questo è il motivo per cui Java ha perso molto del suo fascino per me come programmatore C ++ (mancanza di semantica RAII), ma sto divagando: questa è una domanda C ++.

In effetti, è necessario quando devi lavorare con il codice STL o Boost. Ad esempio, i thread C ++ ( boost::threado std::thread) genereranno un'eccezione per uscire con grazia.

Sei sicuro che il tuo ultimo codice "pronto per la produzione" sia sicuro per le eccezioni?

Puoi essere sicuro che lo sia?

Scrivere un codice sicuro per le eccezioni è come scrivere un codice privo di bug.

Non puoi essere sicuro al 100% che il tuo codice sia sicuro per le eccezioni. Ma poi, ti sforzi per farlo, usando schemi ben noti ed evitando noti anti-schemi.

Conosci e / o effettivamente usi delle alternative che funzionano?

Non ci sono non valide alternative in C ++ (cioè è necessario ritornare alla C ed evitare librerie C ++, così come le sorprese esterni come Windows SEH).

Scrittura del codice sicuro di eccezione

Per scrivere il codice di sicurezza dell'eccezione, devi prima sapere quale livello di sicurezza dell'eccezione è ogni istruzione che scrivi.

Ad esempio, a newpuò generare un'eccezione, ma l'assegnazione di un built-in (ad esempio un int o un puntatore) non fallirà. Uno scambio non fallirà mai (non scrivere mai uno scambio di lancio), un std::list::push_backlancio può ...

Garanzia d'eccezione

La prima cosa da capire è che devi essere in grado di valutare la garanzia di eccezione offerta da tutte le tue funzioni:

  1. nessuno : il tuo codice non dovrebbe mai offrirlo. Questo codice perderà tutto e si interromperà alla prima eccezione generata.
  2. di base : questa è la garanzia che devi offrire per lo meno, cioè se viene generata un'eccezione, nessuna risorsa viene trapelata e tutti gli oggetti sono ancora interi
  3. strong : l'elaborazione avrà esito positivo o genererà un'eccezione, ma se viene generata, i dati saranno nello stesso stato come se l'elaborazione non fosse stata avviata (ciò conferisce una potenza transazionale a C ++)
  4. nothrow / nofail : Il trattamento avrà successo.

Esempio di codice

Il seguente codice sembra corretto C ++, ma in verità offre la garanzia "none" e quindi non è corretto:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

Scrivo tutto il mio codice pensando a questo tipo di analisi.

La garanzia più bassa offerta è di base, ma poi, l'ordinamento di ciascuna istruzione rende l'intera funzione "nessuna", perché se 3. lancia, x perderà.

La prima cosa da fare sarebbe rendere la funzione "di base", ovvero mettere x in un puntatore intelligente fino a quando non è di proprietà dell'elenco in modo sicuro:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Ora, il nostro codice offre una garanzia "base". Nulla perderà e tutti gli oggetti saranno nello stato corretto. Ma potremmo offrire di più, cioè la forte garanzia. È qui che può diventare costoso, ed è per questo che non tutto il codice C ++ è forte. Proviamolo:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Abbiamo riordinato le operazioni, prima creando e impostando Xil valore corretto. Se qualsiasi operazione fallisce, tnon viene modificata, quindi le operazioni da 1 a 3 possono essere considerate "forti": se qualcosa viene lanciato, tnon viene modificato e Xnon perde perché è di proprietà del puntatore intelligente.

Poi, creiamo una copia t2di t, e il lavoro su questa copia dal funzionamento da 4 a 7. Se qualcosa getta, t2viene modificato, ma poi, tè ancora l'originale. Offriamo ancora la forte garanzia.

Quindi, scambiamo te t2. Le operazioni di swap dovrebbero essere nothrow in C ++, quindi speriamo che lo swap per cui hai scritto non Tsia nothrow (in caso contrario, riscriverlo in modo che non sia nothrow).

Quindi, se raggiungiamo la fine della funzione, tutto è riuscito (non è necessario un tipo restituito) e tha il suo valore escluso. Se fallisce, tha ancora il suo valore originale.

Ora, offrire la garanzia forte potrebbe essere piuttosto costoso, quindi non sforzarti di offrire la garanzia forte a tutto il tuo codice, ma se riesci a farlo senza un costo (e l'integrazione del C ++ e altre ottimizzazioni potrebbero rendere tutto il codice sopra senza costi) , allora fallo. L'utente della funzione ti ringrazierà per questo.

Conclusione

Ci vuole una certa abitudine per scrivere un codice sicuro per le eccezioni. Dovrai valutare la garanzia offerta da ciascuna istruzione che utilizzerai, quindi dovrai valutare la garanzia offerta da un elenco di istruzioni.

Naturalmente, il compilatore C ++ non eseguirà il backup della garanzia (nel mio codice, offro la garanzia come tag doxygen @warning), il che è un po 'triste, ma non dovrebbe impedirti di provare a scrivere un codice sicuro per le eccezioni.

Errore normale vs. errore

Come può un programmatore garantire che una funzione no-fail abbia sempre successo? Dopotutto, la funzione potrebbe avere un bug.

Questo è vero. Le garanzie di eccezione dovrebbero essere offerte da un codice privo di bug. Ma poi, in qualsiasi lingua, chiamare una funzione suppone che la funzione sia priva di bug. Nessun codice sano si protegge dalla possibilità che abbia un bug. Scrivi il codice meglio che puoi e poi offri la garanzia supponendo che sia privo di bug. E se c'è un bug, correggilo.

Eccezioni sono per errori di elaborazione eccezionali, non per errori di codice.

Ultime parole

Ora, la domanda è "Ne vale la pena?".

Ovviamente è. Avere una funzione "nothrow / no-fail" sapendo che la funzione non fallirà è un grande vantaggio. Lo stesso si può dire per una funzione "forte", che consente di scrivere codice con semantica transazionale, come database, con funzioni di commit / rollback, il commit è la normale esecuzione del codice, generando eccezioni come rollback.

Quindi, la "base" è la minima garanzia che dovresti offrire. Il C ++ è un linguaggio molto forte lì, con i suoi ambiti, che consente di evitare perdite di risorse (qualcosa che un garbage collector troverebbe difficile offrire per il database, la connessione o gli handle di file).

Quindi, per quanto vedo io, si è valsa la pena.

Modifica 29/01/2010: informazioni sullo scambio senza lancio

Nobar ha fatto un commento che credo sia abbastanza pertinente, perché fa parte di "Come si scrive il codice sicuro di eccezione":

  • [me] Uno scambio non fallirà mai (non scrivere nemmeno uno scambio di lancio)
  • [nobar] Questa è una buona raccomandazione per le swap()funzioni scritte su misura . Va notato, tuttavia, che std::swap()può fallire in base alle operazioni che utilizza internamente

il default std::swapeseguirà copie e assegnazioni che, per alcuni oggetti, possono essere lanciate. Pertanto, lo swap predefinito potrebbe essere utilizzato, utilizzato per le classi o anche per le classi STL. Per quanto riguarda lo standard C ++, l'operazione di scambio per vector, dequee listnon verrà lanciata, mentre potrebbe mapaccadere se il funzione di confronto può lanciare sulla costruzione di copie (Vedi The C ++ Programming Language, Special Edition, appendice E, E.4.3 .Swap ).

Guardando l'implementazione di Visual C ++ 2008 dello swap del vettore, lo swap del vettore non verrà lanciato se i due vettori hanno lo stesso allocatore (cioè il caso normale), ma ne faranno delle copie se hanno allocatori diversi. E quindi, suppongo che potrebbe gettare in quest'ultimo caso.

Quindi, il testo originale è ancora valido: non scrivere mai uno scambio di lancio, ma il commento di nobar deve essere ricordato: assicurati che gli oggetti che stai scambiando abbiano uno scambio di non lancio.

Modifica 2011-11-06: articolo interessante

Dave Abrahams , che ci ha fornito le garanzie di base / forti / nothrow , ha descritto in un articolo la sua esperienza sulla sicurezza dell'eccezione STL:

http://www.boost.org/community/exception_safety.html

Guarda il 7 ° punto (Test automatizzati per la sicurezza delle eccezioni), dove si affida ai test delle unità automatizzate per assicurarsi che ogni caso sia testato. Immagino che questa parte sia un'ottima risposta alla domanda dell'autore " Puoi esserne sicuro, vero? ".

Modifica 31/05/2013: commento di dionadar

t.integer += 1;è senza la garanzia che l'overflow non accadrà NON sicuro, e in effetti può tecnicamente invocare UB! (L'overflow firmato è UB: C ++ 11 5/4 "Se durante la valutazione di un'espressione, il risultato non è definito matematicamente o non è compreso nell'intervallo di valori rappresentabili per il suo tipo, il comportamento non è definito.") Si noti che non firmato i numeri interi non overflow, ma eseguono i loro calcoli in una classe di equivalenza modulo 2 ^ # bit.

Dionadar si riferisce alla seguente riga, che in effetti ha un comportamento indefinito.

   t.integer += 1 ;                 // 1. nothrow/nofail

La soluzione qui è verificare se l'intero è già al suo valore massimo (usando std::numeric_limits<T>::max()) prima di fare l'aggiunta.

Il mio errore andava nella sezione "Errore normale vs. errore", ovvero un errore. Non invalida il ragionamento e non significa che il codice sicuro per le eccezioni sia inutile perché impossibile da raggiungere. Non puoi proteggerti dallo spegnimento del computer, dai bug del compilatore o persino dai tuoi bug o da altri errori. Non puoi raggiungere la perfezione, ma puoi provare ad avvicinarti il ​​più possibile.

Ho corretto il codice tenendo presente il commento di Dionadar.


6
Molte grazie! Speravo ancora qualcosa di diverso. Ma accetto la tua risposta per attenersi al C ++ e per la tua spiegazione dettagliata. Hai persino confutato il mio reato "Questo rende qualsiasi riga di codice C ++ vulnerabile a un'eccezione". Questa analisi riga per riga ha senso dopo tutto ... Devo pensarci.
Frunsi,

8
"Uno scambio non fallirà mai". Questa è una buona raccomandazione per le funzioni swap () personalizzate. Va notato, tuttavia, che std :: swap () può fallire in base alle operazioni che utilizza internamente.
nobar

7
@Mehrdad:: "finally" does exactly what you were trying to achieveCerto che lo è. E poiché è facile produrre codice fragile con finally, il concetto di "prova con risorsa" è stato finalmente introdotto in Java 7 (ovvero 10 anni dopo C # usinge 3 decenni dopo i distruttori C ++). Questa è questa fragilità che critico. Per quanto riguarda Just because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing": In questo, l'industria non è d'accordo con i tuoi gusti, poiché le lingue di Garbage Collected tendono ad aggiungere dichiarazioni ispirate a RAII (C # usinge Java try).
paercebal,

5
@Mehrdad:: RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimesNo, non lo è. È possibile utilizzare i puntatori intelligenti o le classi di utilità per "proteggere" le risorse.
paercebal,

5
@Mehrdad: "ti costringe a creare un wrapper per qualcosa per cui potresti non aver bisogno di un wrapper" - quando si codifica in modo RAII, non sarà necessario alcun tipo di wrapper: la risorsa è l'oggetto e la durata degli oggetti è la durata delle risorse. Tuttavia, io provengo da un mondo C ++, attualmente sto lottando con un progetto Java. Rispetto a RAII in C ++, quella "gestione manuale delle risorse" in Java È UN MESS! La mia opinione, ma Java molto tempo fa ha scambiato "memoria automatica mgmt" con "risorsa automatica mgmt".
Frunsi,

32

Scrivere codice sicuro per le eccezioni in C ++ non significa tanto usare molti blocchi try {} catch {}. Si tratta di documentare il tipo di garanzie fornite dal codice.

Consiglio di leggere la serie Guru della settimana di Herb Sutter , in particolare le puntate 59, 60 e 61.

Per riassumere, ci sono tre livelli di sicurezza delle eccezioni che puoi fornire:

  • Di base: quando il codice genera un'eccezione, il codice non perde le risorse e gli oggetti rimangono distruttibili.
  • Forte: quando il codice genera un'eccezione, lascia invariato lo stato dell'applicazione.
  • Nessun tiro: il tuo codice non genera mai eccezioni.

Personalmente, ho scoperto questi articoli abbastanza tardi, quindi gran parte del mio codice C ++ non è assolutamente sicuro per le eccezioni.


1
Anche il suo libro "Eccezionale C ++" è una buona lettura. Ma provo ancora a mettere in discussione il concetto di EH ...
Frunsi,

8
+1 PO combina la gestione delle eccezioni (catturandole) con la sicurezza dell'eccezione (di solito più su RAII)
jk.

Ho il sospetto che pochissimo codice di produzione C ++ sia eccezionalmente sicuro.
Raedwald,

18

Alcuni di noi usano l'eccezione da oltre 20 anni. PL / I li ha, per esempio. La premessa che sono una tecnologia nuova e pericolosa mi sembra discutibile.


Per favore, non fraintendetemi, sto (o sto provando a) interrogare EH. E soprattutto C ++ EH. E sto cercando alternative. Forse devo accettarlo (e lo farò se è l'unico modo), ma penso che potrebbero esserci alternative migliori. Non è che penso che il concetto sia nuovo, ma sì, penso che possa essere più pericoloso della gestione esplicita degli errori con i codici di ritorno ...
Frunsi,

1
Se non ti piace, non usarlo. Metti blocchi di prova intorno a cose che devi chiamare che possono essere lanciate, reinventa il tuo codice di errore e soffri dei problemi che ha, che in alcuni casi sono tollerabili.
bmargulies,

Ok, perfetto, userò solo EH e codici di errore, e vivrò con esso. Sono un idiota, avrei dovuto giungere a quella soluzione da solo! ;)
Frunsi,

17

Prima di tutto (come affermato da Neil), SEH è la gestione delle eccezioni strutturata di Microsoft. È simile ma non identico all'elaborazione delle eccezioni in C ++. In effetti, è necessario abilitare la gestione delle eccezioni C ++ se lo si desidera in Visual Studio: il comportamento predefinito non garantisce la distruzione degli oggetti locali in tutti i casi! In entrambi i casi, la gestione delle eccezioni non è molto più difficile , è solo diversa .

Ora per le tue domande reali.

Scrivi davvero un codice sicuro?

Sì. Cerco il codice sicuro d'eccezione in tutti i casi. Evangelizzo usando le tecniche RAII per l'accesso mirato alle risorse (ad esempio, boost::shared_ptrper la memoria, boost::lock_guardper il blocco). In generale, l'uso coerente di RAII e le tecniche di protezione dell'ambito renderanno molto più semplice la scrittura di codice sicuro d'eccezione. Il trucco è imparare cosa esiste e come applicarlo.

Sei sicuro che il tuo ultimo codice "pronto per la produzione" sia sicuro per le eccezioni?

No. È sicuro come lo è. Posso dire che non ho visto un errore di processo a causa di un'eccezione in diversi anni di attività 24/7. Non mi aspetto un codice perfetto, solo un codice ben scritto. Oltre a fornire una sicurezza eccezionale, le tecniche sopra descritte garantiscono la correttezza in un modo quasi impossibile da raggiungere con try/ catchblocchi. Se stai rilevando tutto nel tuo ambito di controllo principale (thread, processo, ecc.), Allora puoi essere sicuro che continuerai a correre di fronte alle eccezioni (il più delle volte ). Le stesse tecniche ti aiuteranno anche a continuare a funzionare correttamente di fronte a eccezioni senza try/ catchblocchi ovunque .

Puoi essere sicuro che lo sia?

Sì. Puoi essere sicuro con un controllo approfondito del codice, ma nessuno lo fa davvero? Tuttavia, regolari revisioni del codice e accurati sviluppatori fanno molto per arrivarci.

Conosci e / o effettivamente usi delle alternative che funzionano?

Ho provato alcune varianti nel corso degli anni come la codifica degli stati nei bit superiori (ala HRESULTs ) o setjmp() ... longjmp()quell'hack orribile . Entrambi si rompono in pratica sebbene in modi completamente diversi.


Alla fine, se prendi l'abitudine di applicare alcune tecniche e pensare attentamente a dove puoi effettivamente fare qualcosa in risposta a un'eccezione, finirai con un codice molto leggibile che è sicuro dalle eccezioni. Puoi riassumere seguendo queste regole:

  • Vuoi solo vedere try/ catchquando puoi fare qualcosa per una specifica eccezione
  • Non vuoi quasi mai vedere un codice grezzo newo deletein codice
  • Eschew std::sprintf, snprintfe array in generale: utilizzare std::ostringstreamper formattare e sostituire array con std::vectorestd::string
  • In caso di dubbi, cerca la funzionalità in Boost o STL prima di lanciare il tuo

Posso solo consigliarti di imparare a usare correttamente le eccezioni e di dimenticare i codici dei risultati se prevedi di scrivere in C ++. Se vuoi evitare le eccezioni, potresti prendere in considerazione la possibilità di scrivere in un'altra lingua che non le possiede o le rende sicure . Se vuoi davvero imparare come utilizzare pienamente il C ++, leggi alcuni libri di Herb Sutter , Nicolai Josuttis e Scott Meyers .


"il comportamento predefinito non garantisce che gli oggetti locali vengano distrutti in tutti i casi" Stai dicendo che l'impostazione predefinita del compilatore Visual Studio C ++ produce codice che non è corretto di fronte alle eccezioni. È davvero così?
Raedwald,

"Non vuoi quasi mai vedere un codice raw newo deletein codice": per raw credo che intendi al di fuori di un costruttore o distruttore.
Raedwald

@Raedwald - re: VC ++: l'edizione VS2005 di VC ++ non distruggerà gli oggetti locali quando viene generata un'eccezione SEH. Leggi "abilita gestione eccezioni C ++" . In VS2005, le eccezioni SEH non chiamano i distruttori degli oggetti C ++ per impostazione predefinita. Se chiamate funzioni Win32 o qualsiasi cosa definita in una DLL con interfaccia C, allora dovete preoccuparvi di ciò poiché possono (e occasionalmente) lanciare un'eccezione SEH a modo vostro.
D.Shawley,

@Raedwald: re: raw : in sostanza, deletenon dovrebbe mai essere usato al di fuori dell'implementazione di tr1::shared_ptre simili. newpuò essere utilizzato a condizione che il suo utilizzo sia simile tr1::shared_ptr<X> ptr(new X(arg, arg));. La parte importante è che il risultato di newva direttamente in un puntatore gestito. La pagina sulle boost::shared_ptrmigliori pratiche descrive le migliori.
D.Shawley,

Perché fai riferimento a std :: sprintf (et al) nel tuo set di regole per la sicurezza delle eccezioni? Questi non documentano che generano eccezioni, ad esempio en.cppreference.com/w/cpp/io/c/fprintf
mabraham,

10

Non è possibile scrivere un codice sicuro per le eccezioni partendo dal presupposto che "qualsiasi riga può essere lanciata". La progettazione di un codice sicuro per le eccezioni si basa in modo critico su determinati contratti / garanzie che si prevede che ci si aspetti, osservare, seguire e attuare nel proprio codice. È assolutamente necessario disporre di un codice che non venga mai lanciato. Esistono altri tipi di garanzie eccezionali.

In altre parole, la creazione di un codice sicuro per le eccezioni è in gran parte una questione di progettazione del programma , non solo una semplice codifica .


8
  • Scrivi davvero un codice sicuro?

Bene, ho certamente intenzione di farlo.

  • Sei sicuro che il tuo ultimo codice "pronto per la produzione" sia sicuro per le eccezioni?

Sono sicuro che i miei server 24 ore su 24, 7 giorni su 7, creati utilizzando le eccezioni, funzionano 24 ore su 24, 7 giorni su 7 e non perdono memoria.

  • Puoi essere sicuro che lo sia?

È molto difficile essere sicuri che qualsiasi codice sia corretto. In genere, si può solo andare dai risultati

  • Conosci e / o effettivamente usi delle alternative che funzionano?

No. L'uso delle eccezioni è più semplice e più pulito di qualsiasi altra alternativa che ho usato negli ultimi 30 anni in programmazione.


30
Questa risposta non ha valore.
Matt Joiner,

6
@MattJoiner Quindi la domanda non ha valore.
Miles Rout,

5

Lasciando da parte la confusione tra le eccezioni SEH e C ++, è necessario essere consapevoli del fatto che le eccezioni possono essere generate in qualsiasi momento e scrivere il proprio codice tenendo presente ciò. La necessità della sicurezza delle eccezioni è in gran parte ciò che guida l'uso di RAII, puntatori intelligenti e altre moderne tecniche C ++.

Se segui schemi ben consolidati, scrivere un codice sicuro per le eccezioni non è particolarmente difficile, e in effetti è più facile che scrivere un codice che gestisca correttamente i ritorni degli errori in tutti i casi.


3

EH è buono, in generale. Ma l'implementazione di C ++ non è molto amichevole in quanto è davvero difficile dire quanto sia buona la tua copertura di cattura delle eccezioni. Java, ad esempio, rende tutto più semplice, il compilatore tenderà a fallire se non gestisci possibili eccezioni.


2
È molto meglio con un uso giudizioso di noexcept.
einpoklum,

2

Mi piace molto lavorare con Eclipse e Java (nuovo su Java), perché genera errori nell'editor se manca un gestore EH. Ciò rende le cose MOLTO più difficili da dimenticare per gestire un'eccezione ...

Inoltre, con gli strumenti IDE, aggiunge automaticamente il blocco try / catch o un altro blocco catch.


5
Questa è la differenza tra le eccezioni controllate (Java) e non selezionate (c ++) (anche Java ne ha alcune). Il vantaggio delle eccezioni verificate è quello che hai scritto, ma lì ha i suoi svantaggi. Google per la differenza si avvicina e i diversi problemi che hanno.
David Rodríguez - dribeas,

2

Alcuni di noi preferiscono linguaggi come Java che ci costringono a dichiarare tutte le eccezioni generate dai metodi, invece di renderle invisibili come in C ++ e C #.

Se eseguite correttamente, le eccezioni sono superiori ai codici di ritorno dell'errore, se non altro per non dover propagare manualmente gli errori nella catena di chiamate.

Detto questo, la programmazione della libreria API di basso livello dovrebbe probabilmente evitare la gestione delle eccezioni e attenersi ai codici di ritorno dell'errore.

È stata la mia esperienza che è difficile scrivere codice di gestione delle eccezioni pulito in C ++. Finisco per usare new(nothrow)molto.


4
E stai evitando anche la maggior parte della libreria standard? L'uso new(std::nothrow)non è abbastanza. A proposito, è più facile scrivere un codice sicuro per le eccezioni in C ++ che in Java: en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
avakar

3
L'usabilità dell'eccezione verificata Java è altamente esagerata. In realtà, i linguaggi non Java, NON sono considerati un successo. Questo è il motivo per cui l'istruzione "throw" in C ++ è ora considerata obsoleta e C # non ha mai preso in seria considerazione di implementarli (questa è stata una scelta progettuale). Per Java, il seguente documento potrebbe essere illuminante: googletesting.blogspot.com/2009/09/…
paercebal,

2
La mia esperienza è che scrivere un codice sicuro per le eccezioni in C ++ non è così difficile e che tende a portare a un codice più pulito in generale. Certo, devi imparare a farlo.
David Thornley,

2

Faccio del mio meglio per scrivere un codice sicuro per le eccezioni, sì.

Ciò significa che mi occupo di tenere d'occhio quali linee possono lanciare. Non tutti possono, ed è di fondamentale importanza tenerlo presente. La chiave è davvero pensare e progettare il codice per soddisfare, le garanzie di eccezione definite nello standard.

Questa operazione può essere scritta per fornire la forte garanzia di eccezione? Devo accontentarmi di quello di base? Quali linee possono generare eccezioni e come posso assicurarmi che, se lo fanno, non corrompano l'oggetto?


2
  • Scrivi davvero un codice sicuro? [Non esiste una cosa del genere. Le eccezioni sono una protezione della carta contro gli errori a meno che non si disponga di un ambiente gestito. Questo vale per le prime tre domande.]

  • Conosci e / o effettivamente usi delle alternative che funzionano? [Alternativa a cosa? Il problema qui è che le persone non separano gli errori reali dal normale funzionamento del programma. Se si tratta di un normale funzionamento del programma (ovvero un file non trovato), in realtà non si tratta di gestione degli errori. Se si tratta di un errore reale, non c'è modo di "gestirlo" o non è un errore reale. Il tuo obiettivo qui è scoprire cosa è andato storto e fermare il foglio di calcolo e registrare un errore, riavviare il conducente sul tuo tostapane o semplicemente pregare che il jetfighter possa continuare a volare anche quando il suo software è difettoso e sperare per il meglio.]


0

Molte persone (direi addirittura la maggior parte) lo fanno.

La cosa veramente importante riguardo alle eccezioni è che se non si scrive alcun codice di gestione, il risultato è perfettamente sicuro e ben educato. Troppo desideroso di andare nel panico, ma sicuro.

Devi fare attivamente errori nei gestori per ottenere qualcosa di non sicuro, e solo catturare (...) {} sarà paragonabile all'ignorare il codice di errore.


4
Non vero. È molto semplice scrivere codice non sicuro dalle eccezioni. Ad esempio: f = new foo(); f->doSomething(); delete f;se il metodo doSomething genera un'eccezione, si verifica una perdita di memoria.
Kristopher Johnson,

1
Non importa quando il programma termina, giusto? Per continuare l'esecuzione, dovrai ingoiare attivamente le eccezioni. Bene, ci sono alcuni casi specifici in cui la risoluzione senza ripulitura è ancora inaccettabile, ma tali situazioni richiedono cure particolari in qualsiasi linguaggio e stile di programmazione.
ima,

Non puoi semplicemente ignorare le eccezioni (e non scrivere il codice di gestione), né in C ++ né nel codice gestito. Sarà pericoloso e non si comporterà bene. Tranne forse un po 'di codice giocattolo.
Frunsi,

1
Se si ignorano le eccezioni nel codice dell'applicazione, il programma potrebbe comunque NON funzionare correttamente quando sono coinvolte risorse esterne. È vero, il sistema operativo si preoccupa della chiusura di handle di file, blocchi, socket e così via. Ma non tutto viene gestito, ad esempio potrebbe lasciare file non necessari o danneggiare i file durante la scrittura su di essi e così via. Se ignori le eccezioni, hai un problema in Java, in C ++ puoi lavorare con RAII (ma quando usi la tecnica RAII, molto probabilmente le usi perché ti preoccupi delle eccezioni) ...
Frunsi,

3
Per favore, non distorcere le mie parole. Ho scritto "se non scrivi alcun codice di gestione" - e intendevo esattamente questo. Per ignorare le eccezioni, è necessario scrivere il codice.
ima,
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.