Quando è utile una funzione try block?

Mi chiedo quando i programmatori usano i blocchi di prova della funzione. Quando è utile?

void f(int i) try { if ( i < 0 ) throw "less than zero"; std::cout << "greater than zero" << std::endl; } catch(const char* e) { std::cout << e << std::endl; } int main() { f(1); f(-1); return 0; } 

Uscita: (a ideone )

 greater than zero less than zero 

EDIT: Come alcune persone potrebbero pensare che la syntax della definizione della funzione non sia corretta (perché la syntax non sembra familiare), devo dire che non è corretta. Si chiama function-try-block. Vedi §8.4 / 1 [dcl.fct.def] nello standard C ++.

Lo si utilizza nei costruttori per rilevare errori dagli inizializzatori. Di solito, non si capiscono quegli errori, quindi questo è un uso abbastanza eccezionale.

Altrimenti, è inutile: a meno che non mi sembri sbagliato,

 void f() try { ... } catch (...) { ... } 

è strettamente equivalente a

 void f() { try { ... } catch (...) { ... } } 

I blocchi di prova delle funzioni sono utili per me in due contesti.

a) Per avere una clausola catch tutto attorno a main() consente di scrivere piccole utility senza doversi preoccupare della gestione degli errori locali:

 int main() try { // ... return 0; } catch (...) { // handle errors return -1; } 

che è chiaramente solo zucchero sintattico per avere un tentativo / cattura all’interno di main() stesso.

b) per gestire le eccezioni generate dai costruttori della class base:

 struct B { B() { /*might throw*/ } }; struct A : B { A() try : B() { // ... } catch (...) { // handle exceptions thrown from inside A() or by B() } }; 

A parte gli usi funzionali menzionati, puoi usare il blocco funzione-try per risparmiare un livello di rientro. (Ack, una risposta sugli stili di codifica!)

Tipicamente vedi esempi con il blocco funzione-try in questo modo:

 void f(/*...*/) try { /*...*/ } catch(/*...*/) { /*...*/ } 

Dove l’ambito della funzione è rientrato allo stesso livello come se non esistesse alcun blocco funzione-try. Questo può essere utile quando:

  • hai un limite di 80 caratteri e dovresti avvolgere le linee dato il rientro extra.
  • stai provando ad aggiornare una funzione esistente con try catch e non vuoi toccare tutte le linee della funzione. (Sì, potremmo semplicemente usare git blame -w .)

Però, per le funzioni che sono interamente racchiuse da un blocco di funzione, suggerirei di non alternare tra alcune funzioni usando i blocchi di funzione-try e alcuni non all’interno della stessa base di codice. La coerenza è probabilmente più importante dei problemi di line wrapping. 🙂

Potrebbe essere utile se si desidera rilevare eccezioni dall’inizializzatore del costruttore.

Tuttavia, se rilevi un’eccezione nel costruttore in quel modo, devi ricrearlo o lanciare una nuova eccezione (cioè non puoi tornare normalmente dal costruttore). Se non si rilancia, accade semplicemente implicitamente.

 #include  class A { public: A() try { throw 5; } catch (int) { std::cout << "exception thrown\n"; //return; <- invalid } }; int main() { try { A a; } catch (...) { std::cout << "was rethrown"; } } 

Note su come funzionano i blocchi di prova della funzione:

  • Per i costruttori, un blocco try della funzione comprende la costruzione di membri dati e classi base.

  • Per i distruttori, un blocco di prova della funzione comprende la distruzione dei membri dei dati e delle classi base. Diventa complicato, ma per C ++ 11, devi includere noexcept(false) nella dichiarazione del tuo distruttore (o quella di una class base / membro) o qualsiasi eccezione di distruzione comporterà la chiusura al termine del blocco catch . Potrebbe essere ansible evitare ciò inserendo un’istruzione return nel blocco catch (ma questo non funzionerà per i costruttori).

  • Un catch catch in un costruttore o distruttore deve generare qualche eccezione (o implicitamente ripercuoterà l’eccezione rilevata) . Non è legale return semplicemente (almeno nel blocco catch della funzione del costruttore). Si noti, tuttavia, che è ansible chiamare exit() o simili, il che potrebbe avere senso in alcune situazioni.

  • Un blocco catch non può restituire un valore, quindi non funziona per le funzioni che restituiscono non-void (a meno che non terminino intenzionalmente il programma con exit() o simili). Almeno questo è quello che ho letto .

  • Il blocco catch per una funzione-funzione-costruttore non può fare riferimento a dati / membri di base poiché avranno 1) non sono stati costruiti o 2) sono stati distrutti prima della cattura. Di conseguenza, i blocchi di prova della funzione non sono utili per ripulire lo stato interno di un object: l’object dovrebbe già essere completamente “morto” al momento del suo arrivo. Questo fatto rende molto pericoloso l’uso dei blocchi di funzione nei costruttori, dal momento che è difficile controllare questa regola nel tempo se il / i compilatore / i non capita di contrassegnarlo.

usi (legali) validi

  • Tradurre un’eccezione (in un tipo / messaggio diverso) generata durante il costruttore o i suoi costruttori di base / membri.
  • Traduzione o assorbimento ed eccezione lanciata durante il distruttore o i suoi distruttori di base / membri (nonostante l’ etichetta del distruttore ).
  • Terminare un programma (magari con un messaggio utile).
  • Un qualche tipo di schema di registrazione delle eccezioni.
  • Lo zucchero sintattico per le funzioni di annullamento del vuoto che hanno bisogno di un blocco try / catch completamente incapsulante.

Un’altra cosa che puoi usarli è fornire dati extra durante il debug, in modo da non interferire con la build finita. Non ho visto nessun altro utilizzarlo o sostenerlo, ma è qualcosa che trovo conveniente.

 // Function signature helper. #if defined(_WIN32) || defined(_WIN64) #define FUNC_SIG __FUNCSIG__ #elif defined(__unix__) #define FUNC_SIG __PRETTY_FUNCTION__ // Add other compiler equivalents here. #endif /* Function signature helper. */ void foo(/* whatever */) #ifdef DEBUG try #endif /* DEBUG */ { // ... } #ifdef DEBUG catch(SomeExceptionOrOther& e) { std::cout << "Exception " << e.what() << std::endl << "* In function: " << FUNC_SIG << std::endl << "* With parameters: " << /* output parameters */ << std::endl << "* With internal variables: " << /* output vars */ << std::endl; throw; } #endif /* DEBUG */ 

Ciò consentirebbe sia di ottenere informazioni utili durante il test del codice, sia di manometterlo facilmente senza influire su nulla.