Lancia la parola chiave nella firma della funzione


199

Qual è la ragione tecnica per cui è considerata una cattiva pratica usare la throwparola chiave C ++ nella firma di una funzione?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

Vedere questo recente domanda relativa: stackoverflow.com/questions/1037575/...
laalto


1
Fa noexceptcambiare qualcosa?
Aaron McDaid il

12
Non c'è nulla di sbagliato nell'avere opinioni su qualcosa che riguarda la programmazione. Questo criterio di chiusura è negativo, almeno per questa domanda. È una domanda interessante con risposte interessanti.
Anders Lindén,

Va notato che le specifiche delle eccezioni sono obsolete dal C ++ 11.
François Andrieux,

Risposte:


127

No, non è considerata una buona pratica. Al contrario, è generalmente considerata una cattiva idea.

http://www.gotw.ca/publications/mill22.htm fornisce ulteriori dettagli sul perché, ma il problema è in parte il fatto che il compilatore non è in grado di applicarlo, quindi deve essere verificato in fase di esecuzione, che di solito è indesiderabile. E non è ben supportato in ogni caso. (MSVC ignora le specifiche delle eccezioni, ad eccezione di throw (), che interpreta come garanzia che non verrà generata alcuna eccezione.


25
Sì. Esistono modi migliori per aggiungere spazi bianchi al codice rispetto a lanciare (myEx).
Assaf Lavie,

4
sì, le persone che hanno appena scoperto le specifiche delle eccezioni spesso presumono che funzionino come in Java, dove il compilatore è in grado di applicarle. In C ++, ciò non accadrà, il che li rende molto meno utili.
jalf

7
Ma che dire dello scopo documentativo allora? Inoltre, ti verrà detto quali eccezioni non otterrai mai anche se ci provi.
Anders Lindén,

1
@ AndersLindén quale scopo documentativo? Se vuoi solo documentare il comportamento del tuo codice, basta inserire un commento sopra di esso. Non sono sicuro di cosa intendi con la seconda parte. Eccezioni che non prenderai mai anche se ci provi?
jalf

4
Penso che il codice sia potente quando si tratta di documentare il codice. (i commenti possono mentire). L'effetto "documentativo" a cui mi riferisco è che saprai con certezza quali eccezioni puoi catturare e altre no.
Anders Lindén

57

Jalf è già collegato ad esso, ma il GOTW afferma abbastanza bene perché le specifiche delle eccezioni non sono così utili come si potrebbe sperare:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

I commenti sono corretti? Non proprio. Gunc()potrebbe davvero lanciare qualcosa e Hunc()potrebbe anche lanciare qualcosa di diverso da A o B! Il compilatore garantisce solo di batterli senza senso se lo fanno ... oh, e anche di battere il tuo programma senza senso, il più delle volte.

Questo è esattamente ciò a cui si riduce, probabilmente finirai con una chiamata terminate()e il tuo programma morirà in modo rapido ma doloroso.

La conclusione dei GOTW è:

Quindi ecco quello che sembra essere il miglior consiglio che noi come comunità abbiamo imparato da oggi:

  • Morale n. 1: non scrivere mai una specifica di eccezione.
  • Morale # 2: Tranne forse uno vuoto, ma se fossi in te eviterei anche quello.

1
Non sono sicuro del motivo per cui dovrei lanciare un'eccezione e non potrei parlarne. Anche se viene generato da un'altra funzione, so quali eccezioni potrebbero essere generate. L'unica ragione per cui riesco a vedere è perché è noioso.
MasterMastic,

3
@Ken: Il punto è che la scrittura di specifiche di eccezione ha conseguenze principalmente negative. L'unico effetto positivo è che mostra al programmatore quali eccezioni possono verificarsi, ma poiché non è verificato dal compilatore in modo ragionevole è soggetto a errori e quindi non vale molto.
qc

1
Oh ok, grazie per aver risposto. Immagino sia a questo che serve la documentazione.
MasterMastic

2
Non corretto. È necessario scrivere la specifica dell'eccezione, ma l'idea è quella di comunicare quali errori il chiamante dovrebbe tentare di rilevare.
HelloWorld,

1
Proprio come dice @StudentT: è responsabilità della funzione garantire di non gettare ulteriori eccezioni. In tal caso, il programma termina come dovrebbe. Dichiarare il lancio significa che non è mia responsabilità gestire questa situazione e il chiamante dovrebbe avere abbastanza informazioni per farlo. Non dichiarare eccezioni significa che possono verificarsi ovunque e potrebbero essere gestite ovunque. Questo è sicuramente un disordine anti-OOP. È incapace di progettare catturare eccezioni in posti sbagliati. Consiglio di non svuotare, poiché le eccezioni sono eccezionali e la maggior parte delle funzioni dovrebbe svuotarsi comunque.
Jan Turoň,

30

Per aggiungere un po 'più di valore a tutte le altre risposte a questa domanda, si dovrebbe investire qualche minuto nella domanda: qual è l'output del seguente codice?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Risposta: Come notato qui , il programma chiama std::terminate()e quindi nessuno dei gestori delle eccezioni verrà chiamato.

Dettagli: my_unexpected()viene chiamata la prima funzione, ma poiché non genera nuovamente un tipo di eccezione corrispondente per il throw_exception()prototipo della funzione, alla fine std::terminate()viene chiamata. Quindi l'output completo è simile al seguente:

user @ user: ~ / tmp $ g ++ -o tranne.testcept.test.cpp
user @ user: ~ / tmp $ ./except.test
bene - questo è stato un
termine imprevisto chiamato dopo aver lanciato un'istanza di 'int'
Aborted (core scaricati)


12

L'unico effetto pratico dell'identificatore del tiro è che se qualcosa di diverso myExcviene lanciato dalla tua funzione, std::unexpectedverrà chiamato (invece del normale meccanismo di eccezione non gestita).

Per documentare il tipo di eccezioni che una funzione può generare, in genere faccio questo:

bool
some_func() /* throw (myExc) */ {
}

5
È anche utile notare che una chiamata a std :: unexpected () di solito provoca una chiamata a std :: terminate () e la fine improvvisa del programma.
sth,

1
- e che MSVC almeno non attua questo comportamento per quanto ne so.
jalf,

9

Quando le specifiche del tiro sono state aggiunte al linguaggio, era con le migliori intenzioni, ma la pratica ha dato prova di un approccio più pratico.

Con C ++, la mia regola generale è quella di utilizzare solo le specifiche di lancio per indicare che un metodo non può essere lanciato. Questa è una forte garanzia. Altrimenti, supponiamo che potrebbe lanciare qualsiasi cosa.


9

Bene, mentre cercavo su Google questa specifica del tiro, ho dato un'occhiata a questo articolo: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

Ne sto riproducendo una parte anche qui, in modo che possa essere utilizzato in futuro indipendentemente dal fatto che il link sopra funzioni o meno.

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Quando il compilatore vede questo, con l'attributo "throw ()", il compilatore può ottimizzare completamente la variabile "bar", perché sa che non c'è modo di generare un'eccezione da MethodThatCannotThrow (). Senza l'attributo throw (), il compilatore deve creare la variabile "bar", perché se MethodThatCannotThrow genera un'eccezione, il gestore delle eccezioni potrebbe dipendere / dipenderà dal valore della variabile bar.

Inoltre, strumenti di analisi del codice sorgente come prefast possono (e useranno) l'annotazione throw () per migliorare le loro capacità di rilevamento degli errori - ad esempio, se hai un tentativo / cattura e tutte le funzioni che chiami sono contrassegnate come throw (), non hai bisogno di try / catch (sì, questo ha un problema se in seguito chiami una funzione che potrebbe essere lanciata).


3

Una specifica no throw su una funzione incorporata che restituisce solo una variabile membro e che potrebbe non generare eccezioni può essere utilizzata da alcuni compilatori per fare pessimizzazioni (una parola inventata per l'opposto di ottimizzazioni) che può avere un effetto dannoso sulle prestazioni. Questo è descritto nella letteratura Boost: specifica delle eccezioni

Con alcuni compilatori una specifica no-throw su funzioni non in linea può essere utile se vengono apportate le ottimizzazioni corrette e l'uso di tale funzione influisce sulle prestazioni in un modo che lo giustifica.

Per me sembra che usarlo o meno sia una chiamata fatta da un occhio molto critico come parte di uno sforzo di ottimizzazione delle prestazioni, forse usando strumenti di profilazione.

Una citazione dal link sopra per quelli che vanno di fretta (contiene un esempio di cattivi effetti non voluti di specificare il lancio di una funzione inline da un compilatore ingenuo):

Logica delle specifiche di eccezione

Le specifiche di eccezione [ISO 15.4] sono talvolta codificate per indicare quali eccezioni possono essere generate o perché il programmatore spera che possano migliorare le prestazioni. Ma considera il seguente membro da un puntatore intelligente:

T & operator * () const throw () {return * ptr; }

Questa funzione non chiama altre funzioni; manipola solo tipi di dati fondamentali come puntatori Pertanto, nessun comportamento di runtime della specifica di eccezione può mai essere invocato. La funzione è completamente esposta al compilatore; infatti è dichiarato in linea. Pertanto, un compilatore intelligente può facilmente dedurre che le funzioni non sono in grado di generare eccezioni e apportare le stesse ottimizzazioni che avrebbe apportato sulla base della specifica vuota dell'eccezione. Un compilatore "stupido", tuttavia, può effettuare tutti i tipi di pessimizzazioni.

Ad esempio, alcuni compilatori disattivano l'inline se esiste una specifica di eccezione. Alcuni compilatori aggiungono blocchi try / catch. Tali pessimizzazioni possono essere un disastro delle prestazioni che rende il codice inutilizzabile in applicazioni pratiche.

Sebbene inizialmente attraente, una specifica di eccezione tende ad avere conseguenze che richiedono una riflessione molto attenta per capire. Il problema più grande con le specifiche delle eccezioni è che i programmatori li usano come se avessero l'effetto che il programmatore vorrebbe, invece dell'effetto che effettivamente hanno.

Una funzione non in linea è il punto in cui una specifica di eccezione "non genera nulla" può avere alcuni vantaggi con alcuni compilatori.


1
"Il problema più grande con le specifiche delle eccezioni è che i programmatori li usano come se avessero l'effetto che il programmatore vorrebbe, anziché l'effetto che effettivamente hanno." Questa è la ragione numero 1 per gli errori, quando si comunica con macchine o persone: la differenza tra ciò che diciamo e ciò che intendiamo.
Thagomizer,
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.