2015-02-24 3 views
11

Sto implementando callback in C++ che verranno chiamati dal normale codice C. La mia funzione main() è già C++, ma il codice C sarà responsabile della creazione di thread che alla fine chiameranno i miei callback.try {} catch (...) {} in callback C - cattiva idea?

In questo momento i miei callback sembrano

int handle_foo(void *userdata) { 
    try { 
     MyCPPClass *obj = static_cast<MyCPPClass *>(userdata); 
     obj->doStuff(); 
     return 0; // no error 
    } catch(...) { 
     LogError("doStuff failed"); 
     return -1; // error 
    } 
} 

Questo funziona bene, ma sembra strano per me. Inoltre, perdo alcune funzionalità utili come la possibilità di scoprire che cosa è stato generato (senza aggiungere un'enorme quantità di ulteriori dichiarazioni catch a ciascuna delle mie richiamate).

È try {} catch(...) {} qui ragionevole, o c'è un modo migliore per scrivere i miei callback C?

+0

Se l'evento che 'doStuff()' genera un'eccezione, se c'è qualcosa che il tuo programma C chiamante può effettivamente fare per continuare? Se non può funzionare intorno all'eccezione, non c'è alcun vantaggio ad esso con l'eccezione IMO. Avere la buona conoscenza che il tuo 'handle_foo' non ha intenzione di gettare renderà il tuo codice chiamante molto più facile da leggere. – TZHX

+1

È necessario impedire a qualsiasi eccezione di lasciare una funzione chiamata da una funzione C e provare a catturare è il modo per farlo. Se hai bisogno della tua eccezione per "attraversare" un livello C, puoi usare un'eccezione globale ptr (non dire che questo sarebbe un buon stile). Essenzialmente questo ti dà un meccanismo di errno più potente, ma devi comunque ccheckmanually per un errore quando il tuo c-code ritorna. – MikeMB

+1

'Inoltre, perdo alcune funzioni utili come la capacità di scoprire cosa è stato lanciato. Catch 'std :: exception' e log' exception :: what'. Non lanciare nulla che non sia derivato da 'std :: exception'. – sbabbi

risposta

7

Sì, devi prendere le eccezioni e, si spera, tradurle in qualcosa di utile. Lasciando che le eccezioni si propagano attraverso il codice C porta a comportamenti non definiti. Nella migliore delle ipotesi non puoi aspettarti che il codice C mantenga lo stato coerente del programma.

Vedere this answer per un esempio semplice. Un esempio più difficile è con alcuni software complessi come SQLite - Il codice C prenderà alcuni mutex e non lo rilascerà perché l'eccezione semplicemente "vola attraverso" e il tuo programma ora è brindato.

Anche questo ha qualche possibilità di "funzionare" se tutto il codice è costruito sullo stesso runtime di C++. Se hai attivato la funzione di callback, ad esempio Visual C++ 9 e il resto del codice, ad esempio Visual C++ 10 o quelle parti sono compilate su librerie di runtime statiche, ora hai due distinti runtime e l'eccezione non gestita nel callback causa la chiamata di terminate() .

+0

"... is not toast" dovrebbe probabilmente dire "... ora è toast" ... –

+0

@MatsPetersson: Grazie. – sharptooth

+0

Grazie per aver confermato i miei sospetti. Quindi, non è solo una buona idea catturare tutte le eccezioni, è in realtà richiesto. Che dire della determinazione della natura dell'eccezione? Non esiste un metodo generale oltre a scomporre le catture in casi separati? – nneonneo

3

sharptooth ha risposto alla maggior parte della tua domanda, e James McNellis ha scritto un bel blog post su come liberarsi elegantemente del boilerplate usando il C++ moderno.

L'essenza di esso sta avendo una funzione translate_exceptions utilizzato simili:

int callback(...) { 
    return translate_exceptions([&]{ 
     // ... your code here 
    }); 
} 

Il translate_exception è un modello semplice funzione (che utilizza un blocco livello funzione try) prendendo un richiamabile:

template<typename Fn> int translate_exception(Fn fn) try { 
    fn(); 
    return 0; 
} catch(std::exception const& e) { 
    LOG("[EE] exception ", e.what()); 
    return -2; 
} catch(...) { 
    LOG("[EE] unknown exception"); 
    return -1; 
}