Devo usare uno specificatore di eccezione in C ++?


123

In C ++, è possibile specificare che una funzione può o non può generare un'eccezione utilizzando uno specificatore di eccezione. Per esempio:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Sono dubbioso sull'utilizzo effettivo di loro a causa di quanto segue:

  1. Il compilatore non impone gli specificatori di eccezioni in modo rigoroso, quindi i vantaggi non sono grandi. Idealmente, vorresti ricevere un errore di compilazione.
  2. Se una funzione viola uno specificatore di eccezione, penso che il comportamento standard sia terminare il programma.
  3. In VS.Net, tratta lancio (X) come lancio (...), quindi l'aderenza allo standard non è forte.

Pensi che dovrebbero essere usati gli specificatori di eccezione?
Rispondi con "sì" o "no" e fornisci alcuni motivi per giustificare la tua risposta.


7
"throw (...)" non è lo standard C ++. Credo che sia un'estensione in alcuni compilatori e generalmente ha lo stesso significato di nessuna specifica di eccezione.
Richard Corden

Risposte:


97

No.

Ecco alcuni esempi di perché:

  1. Il codice del modello è impossibile da scrivere con le specifiche di eccezione,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Le copie potrebbero generare, il passaggio di parametri potrebbe generare e x()potrebbe generare un'eccezione sconosciuta.

  2. Le specifiche di eccezione tendono a vietare l'estensibilità.

    virtual void open() throw( FileNotFound );

    potrebbe evolversi in

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Potresti davvero scriverlo come

    throw( ... )

    Il primo non è estensibile, il secondo è troppo ambizioso e il terzo è davvero quello che intendi quando scrivi funzioni virtuali.

  3. Codice precedente

    Quando scrivi codice che si basa su un'altra libreria, non sai davvero cosa potrebbe fare quando qualcosa va terribilmente storto.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gterminerà, quando lib_f()lancia. Questo (nella maggior parte dei casi) non è quello che vuoi veramente. std::terminate()non dovrebbe mai essere chiamato. È sempre meglio lasciare che l'applicazione si blocchi con un'eccezione non gestita, da cui è possibile recuperare una traccia dello stack, piuttosto che morire silenziosamente / violentemente.

  4. Scrivi codice che restituisca errori comuni e lanci in occasioni eccezionali.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

Tuttavia, quando la tua libreria genera solo le tue eccezioni, puoi utilizzare le specifiche delle eccezioni per dichiarare il tuo intento.


1
in 3, tecnicamente sarebbe std :: imprevisto non std :: terminate. Ma quando viene chiamata questa funzione, l'impostazione predefinita richiama abort (). Questo genera un core dump. Come è peggio di un'eccezione non gestita? (che fa praticamente la stessa cosa)
Greg Rogers

6
@ Greg Rogers: un'eccezione non rilevata continua a sciogliere lo stack. Ciò significa che verranno chiamati i distruttori. E in quei distruttori, si può fare molto, come: risorse correttamente liberate, log scritti correttamente, ad altri processi verrà detto che il processo corrente sta andando in crash, ecc. Per riassumere, è RAII.
paercebal

Hai tralasciato: questo avvolge efficacemente tutto in un try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]costrutto, indipendentemente dal fatto che tu voglia o meno un blocco try lì.
David Thornley

4
@paercebal Non è corretto. È l'implementazione definita se i distruttori vengono eseguiti o meno per eccezioni non rilevate. La maggior parte degli ambienti non sblocca i distruttori stack / run se l'eccezione non viene rilevata. Se vuoi assicurarti in modo portabile che i tuoi distruttori funzionino anche quando un'eccezione viene lanciata e non gestita (questo è di dubbio valore), devi scrivere il tuo codice cometry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo

" È sempre meglio lasciare che l'applicazione si blocchi con un'eccezione non gestita, da cui è possibile recuperare una traccia dello stack, piuttosto che morire silenziosamente / violentemente. " Come può un "arresto anomalo" essere migliore di una chiamata pulita a terminate()? Perché non chiami e basta abort()?
curioso

42

Evita le specifiche di eccezione in C ++. Le ragioni che fornisci nella tua domanda sono un buon inizio per il perché.

Vedi Herb Sutter's "Uno sguardo pragmatico alle specifiche delle eccezioni" di .


3
@awoodland: l'uso di 'dynamic-exception-specification' ( throw(optional-type-id-list)) è deprecato in C ++ 11. Sono ancora nello standard, ma immagino che sia stato inviato un avvertimento che il loro uso dovrebbe essere considerato attentamente. C ++ 11 aggiunge la noexceptspecifica e l'operatore. Non conosco abbastanza dettagli noexceptper commentarlo. Questo articolo sembra essere piuttosto dettagliato: akrzemi1.wordpress.com/2011/06/10/using-noexcept E Dietmar Kühl ha un articolo sull'Overload
Michael Burr

@MichaelBurr Only throw(something)è considerato inutile e una cattiva idea. throw()è utile.
curioso

" Ecco cosa molte persone pensano che le specifiche delle eccezioni facciano: bullet Garantire che le funzioni genereranno solo eccezioni elencate (possibilmente nessuna). Bullet Abilitare le ottimizzazioni del compilatore in base alla consapevolezza che verranno lanciate solo le eccezioni elencate (possibilmente nessuna). Le aspettative di cui sopra sono, ancora una volta, ingannevolmente vicino all'essere corretto "No, le aspettative di cui sopra sono assolutamente corrette.
curioso

14

Penso che gli specificatori di eccezione standard ad eccezione della convenzione (per C ++)
fossero un esperimento nello standard C ++ che per lo più fallì.
L'eccezione è che lo specificatore no throw è utile, ma dovresti anche aggiungere internamente il blocco try catch appropriato per assicurarti che il codice corrisponda allo specificatore. Herb Sutter ha una pagina sull'argomento. Gotch 82

Inoltre, penso che valga la pena descrivere le garanzie di eccezione.

Questi sono fondamentalmente la documentazione su come lo stato di un oggetto è influenzato dalle eccezioni che escono da un metodo su quell'oggetto. Sfortunatamente non vengono applicati o altrimenti menzionati dal compilatore.
Boost ed eccezioni

Garanzie di eccezione

Nessuna garanzia:

Non vi è alcuna garanzia sullo stato dell'oggetto dopo che un'eccezione è sfuggita a un metodo
In queste situazioni l'oggetto non dovrebbe più essere utilizzato.

Garanzia di base:

In quasi tutte le situazioni questa dovrebbe essere la garanzia minima fornita da un metodo.
Ciò garantisce che lo stato dell'oggetto sia ben definito e possa ancora essere utilizzato in modo coerente.

Garanzia forte: (nota anche come garanzia transazionale)

Ciò garantisce che il metodo verrà completato correttamente
o verrà generata un'eccezione e lo stato degli oggetti non cambierà.

Nessuna garanzia di tiro:

Il metodo garantisce che non sia consentita la propagazione delle eccezioni al di fuori del metodo.
Tutti i distruttori dovrebbero fornire questa garanzia.
| NB Se un'eccezione sfugge a un distruttore mentre un'eccezione si sta già propagando
| l'applicazione verrà chiusa


Le garanzie sono qualcosa che ogni programmatore C ++ deve sapere, ma non mi sembra che siano correlate alle specifiche delle eccezioni.
David Thornley

1
@David Thornley: vedo le garanzie come quello che avrebbero dovuto essere le specifiche di eccezione (cioè un metodo con la G forte non può chiamare un metodo con una G di base senza protezione). Sfortunatamente non sono sicuro che siano definiti abbastanza bene da essere applicati in modo utile dal compilatore.
Martin York

" Ecco cosa molte persone pensano che le specifiche delle eccezioni facciano: - Garantire che le funzioni generino solo eccezioni elencate (possibilmente nessuna). - Abilitare le ottimizzazioni del compilatore in base alla consapevolezza che verranno lanciate solo le eccezioni elencate (possibilmente nessuna). Le aspettative di cui sopra sono, di nuovo, ingannevolmente vicino all'essere corretto. "In realtà, entrambi hanno esattamente ragione.
curioso

@curiousguy. È così che fa Java perché i controlli vengono applicati in fase di compilazione. C ++ sono controlli di runtime. Quindi: Guarantee that functions will only throw listed exceptions (possibly none). Non vero. Garantisce solo che se la funzione genera queste eccezioni, l'applicazione verrà terminata. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownNon posso farlo. Perché ho appena sottolineato che non puoi garantire che l'eccezione non verrà lanciata.
Martin York

1
@LokiAstari "È così che fa Java perché i controlli vengono applicati in fase di compilazione_" La specifica dell'eccezione Java è un esperimento fallito. Java non ha la specifica di eccezione più utile: throw()(non genera). " Garantisce solo che se la funzione genera queste eccezioni l'applicazione terminerà " No "non vero" significa vero. Non esiste alcuna garanzia in C ++ che una funzione non venga chiamata terminate(), mai. " Perché ho appena sottolineato che non puoi garantire che l'eccezione non verrà lanciata " Garantisci che la funzione non verrà lanciata . Che è esattamente ciò di cui hai bisogno.
curioso

8

gcc emetterà avvisi quando violate le specifiche di eccezione. Quello che faccio è utilizzare le macro per utilizzare le specifiche delle eccezioni solo in modalità "lint" compilare espressamente per verificare che le eccezioni siano d'accordo con la mia documentazione.


7

L'unico identificatore di eccezione utile è "throw ()", come in "non lancia".


2
Puoi aggiungere un motivo per cui è utile?
buti-oxa

2
perché non è utile? non c'è niente di più utile che sapere che qualche funzione non inizierà a generare eccezioni a sinistra, a destra e al centro.
Matt Joiner

1
Si prega di consultare la discussione di Herb Sutter a cui si fa riferimento nella risposta di Michael Burr per una spiegazione dettagliata.
Harold Ekstrom,

4

Le specifiche delle eccezioni non sono strumenti straordinariamente utili in C ++. Tuttavia, c'è / un buon uso per loro, se combinati con std :: inaspettato.

Quello che faccio in alcuni progetti è codice con specifiche di eccezione, quindi chiamo set_unexpected () con una funzione che genererà un'eccezione speciale del mio progetto. Questa eccezione, al momento della costruzione, ottiene un backtrace (in un modo specifico della piattaforma) ed è derivato da std :: bad_exception (per consentirne la propagazione se lo si desidera). Se provoca una chiamata a terminate (), come fa di solito, il backtrace viene stampato da what () (così come l'eccezione originale che lo ha causato; non è difficile trovarlo) e quindi ottengo informazioni su dove si trovava il mio contratto violata, ad esempio quale eccezione imprevista della libreria è stata generata.

Se lo faccio, non permetto mai la propagazione delle eccezioni della libreria (eccetto quelle std) e ricavo tutte le mie eccezioni da std :: exception. Se una libreria decide di lanciare, catturerò e convertirò nella mia gerarchia, permettendomi di controllare sempre il codice. Le funzioni basate su modelli che chiamano funzioni dipendenti dovrebbero evitare le specifiche di eccezione per ovvie ragioni; ma è comunque raro avere un'interfaccia di funzione basata su modelli con il codice della libreria (e poche librerie usano davvero i modelli in modo utile).


3

Se stai scrivendo codice che verrà utilizzato da persone che preferirebbero guardare la dichiarazione della funzione piuttosto che qualsiasi commento attorno ad essa, una specifica dirà loro quali eccezioni potrebbero voler catturare.

Altrimenti non trovo particolarmente utile utilizzare altro se non throw()per indicare che non genera eccezioni.


3

No. Se li usi e viene generata un'eccezione che non hai specificato, dal tuo codice o dal codice chiamato dal tuo codice, il comportamento predefinito è terminare prontamente il tuo programma.

Inoltre, credo che il loro uso sia stato deprecato nelle attuali bozze dello standard C ++ 0x.



2

Generalmente non userei specificatori di eccezioni. Tuttavia, nei casi in cui se dalla funzione in questione derivasse qualsiasi altra eccezione, il programma non sarebbe definitivamente in grado di correggere , allora può essere utile. In tutti i casi, assicurati di documentare chiaramente quali eccezioni ci si possono aspettare da quella funzione.

Sì, il comportamento previsto di un'eccezione non specificata generata da una funzione con specificatori di eccezione è chiamare terminate ().

Noterò anche che Scott Meyers affronta questo argomento in C ++ più efficace. Il suo Effective C ++ e More Effective C ++ sono libri altamente raccomandati.


2

Sì, se ti interessa la documentazione interna. O magari scrivere una libreria che altri useranno, in modo che possano raccontare cosa succede senza consultare la documentazione. Lanciare o non lanciare può essere considerato parte dell'API, quasi come il valore restituito.

Sono d'accordo, non sono veramente utili per imporre la correttezza dello stile Java nel compilatore, ma è meglio di niente o commenti casuali.


2

Possono essere utili per i test unitari in modo che quando si scrivono i test si sappia cosa aspettarsi che la funzione lanci quando fallisce, ma non c'è alcuna applicazione che li circonda nel compilatore. Penso che siano codice extra che non è necessario in C ++. Qualunque cosa tu scelga, tutto ciò di cui dovresti essere sicuro è che segui lo stesso standard di codifica in tutto il progetto e nei membri del team in modo che il tuo codice rimanga leggibile.


0

Dall'articolo:

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

"È risaputo che è impossibile scrivere un contenitore generico sicuro rispetto alle eccezioni." Questa affermazione è spesso ascoltata con riferimento a un articolo di Tom Cargill [4] in cui esplora il problema della sicurezza dalle eccezioni per un modello di stack generico. Nel suo articolo, Cargill solleva molte domande utili, ma sfortunatamente non riesce a presentare una soluzione al suo problema.1 Conclude suggerendo che una soluzione potrebbe non essere possibile. Sfortunatamente, il suo articolo è stato letto da molti come una "prova" di quella speculazione. Da quando è stato pubblicato, ci sono stati molti esempi di componenti generici sicuri dalle eccezioni, tra cui i contenitori di librerie standard C ++.

E in effetti posso pensare a modi per rendere sicure le eccezioni delle classi modello. A meno che tu non abbia il controllo su tutte le sottoclassi, potresti comunque avere un problema. Per fare questo si potrebbe creare typedef nelle proprie classi che definiscono le eccezioni lanciate da varie classi template. Questo credo che il problema sia come sempre affrontarlo in seguito invece di progettarlo dall'inizio, e penso che sia questo sovraccarico il vero ostacolo.


-2

Specifiche di eccezione = spazzatura, chiedi a qualsiasi sviluppatore Java di età superiore ai 30 anni


8
I programmatori java di età superiore ai 30 anni dovrebbero sentirsi male per non poter far fronte a C, non hanno scuse per usare java.
Matt Joiner
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.