2010-06-01 15 views
13

Sto scrivendo un software reattivo, che riceve più volte di input, elabora ed emette relativa uscita. Il ciclo principale sembra qualcosa di simile:Alternativa a C++ eccezione

initialize(); 
while (true) { 
    Message msg,out; 
    recieve(msg); 
    process(msg,out); 
    //no global state is saved between loop iterations! 
    send(out); 
} 

voglio che tutto ciò che l'errore si è verificato durante la fase di proccess, whetehr è errore di memoria, errore logico, l'affermazione non valida, ecc, il programma ripulire tutto ciò che ha fatto, e continuare a correre. Suppongo che sia un input non valido e semplicemente lo ignoro.

L'eccezione di C++ è eccezionalmente buona per quella situazione, potrei circondare process con la clausola try/catch e generare un'eccezione ogni volta che qualcosa va in wrog. L'unica cosa di cui ho bisogno per assicurarmi di pulire tutte le mie risorse prima di lanciare un'eccezione. Questo potrebbe essere verificato da RAII, o scrivendo un allocatore di risorse globali (ad esempio, se il tuo distruttore potrebbe lanciare un'eccezione), e usarlo esclusivamente per tutte le risorse.

Socket s = GlobalResourceHandler.manageSocket(new Socket()); 
... 
try { 
    process(msg,out); 
catch (...) { 
    GlobalResourceHandler.cleanUp(); 
} 

Tuttavia, utilizzando eccezione è vietato nel nostro standard di codifica (anche in Google's C++ standard BTW), di conseguenza tutto il codice è stato compilato con le eccezioni al largo, e credo che nessuno sta andando a cambiare il modo di lavorare tutto solo per il mio problema di progettazione.

Inoltre, questo è il codice per la piattaforma embedded, in modo che la funzione ++ meno C in più si usa, più velocemente il codice diventa, e il più portabile che è.

C'è un progetto alternativo che posso prendere in considerazione?

aggiornamento: Apprezzo la risposta di tutti su standard di codice idiota. L'unica cosa che posso dire è che nelle grandi organizzazioni devi avere regole severe e talvolta illogiche, per assicurarti che non ci sia un idiota e rendere il tuo buon codice non mantenibile. Lo standard riguarda più le persone che i dettagli tecnici. Sì, l'uomo cattivo può rendere ogni codice un disastro, ma è molto peggio se gli dai strumenti extra per il compito.

Sto ancora cercando un tecnico risposta .

+3

La prego di fornire riferimento alla norma ++ C del Google che vieta le eccezioni? –

+7

Il modo corretto di farlo con le eccezioni non è tramite l'allocatore globale, ma usando gli oggetti RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization). –

+2

@Pavel: triste ma vero, http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions –

risposta

7

Codificare questo tipo di servizi per tutto il giorno capisco il tuo problema Sebbene abbiamo delle eccezioni all'interno del nostro codice , non li ritorniamo alle librerie esterne che invocano, invece abbiamo una semplice 'tribool'.

enum ReturnCode 
{ 
    OK = 0, // OK (there is a reason for it to be 0) 
    KO,  // An error occurred, wait for next message 
    FATAL // A critical error occurred, reboot 
}; 

Devo dire che FATAL è ... eccezionale. Non c'è alcun percorso di codice nell'applicazione che restituisce, a parte l'inizializzazione (non si può fare molto se non si è inizializzato correttamente).

C++ qui porta molto con Raii, dal momento che ride più percorsi di ritorno esclusa e garantisce il rilascio deterministico degli oggetti in suo possesso.

Ai fini del controllo codice vero e proprio, si può semplicemente utilizzare alcune macro:

// Here is the reason for OK being 0 and KO and Fatal being something else 

#define CHECK_RETURN(Expr) if (ReturnCode code = (Expr)) return code; 

#define CHECK_BREAK(Expr) if (ReturnCode code = (Expr)) \ 
    if (KO == code) break; else return code; 

Poi si possono usare in questo modo:

CHECK_RETURN(initialize()) 
while(true) 
{ 
    Message msg,out; 
    CHECK_BREAK(receive(msg)) 
    CHECK_BREAK(process(msg,out)) 
    CHECK_BREAK(send(out)) 
} 

Come notato, la vera delusione è di circa costruttori. Non puoi avere costruttori "normali" con una tale situazione.

Forse è possibile utilizzare boost::optional, se non è possibile, vorrei davvero suggerire la duplicazione della funzionalità. Che si combinano con le funzioni di fabbrica sistemiche in luogo di costruttori e si è pronti per andare:

boost::optional<MyObject> obj = MyObject::Build(1, 2, 3); 
if (!obj) return KO; 

obj->foo(); 

sembra molto simile a un puntatore, tranne che stack allocato e quindi coinvolge nei pressi di zero overhead.

+0

Grazie! Questo è quello che stavo cercando! In realtà ho pensato a qualcosa in questa forma Il mio unico problema sono le funzioni che restituiscono l'output. Non è così bello convertire tutti codice 'if (isPolygonClockWise (poly) && isPolygonSimple (poly))' a 'bool simple, cw; CHECK_RETURN (isPolygonSimple (poly, & simple)); CHECK_RETURN (isPolygonClockWise (poly, if (simple && cw)' –

+0

Ricorda che la maggior parte le funzioni non generano nulla, se convalidate in prima persona (o anche meglio, assicuratevi che poly sia sempre valido) allora quelle 2 funzioni non dovrebbero mai aver bisogno di lanciare e stanno perfettamente bene restituendo un booleano. –

0

Se si esegue sotto le finestre, è possibile utilizzare le eccezioni SEH. Hanno anche il vantaggio del gestore pre-stack-unwind che può interrompere lo svolgimento (EXCEPTION_CONTINUE_EXECUTION).

+1

Immagino che se C++ non è permesso, allora anche SEH non lo sarà, specialmente visto che non ripuliranno lo stack a meno che tu non abbia abilitato anche la gestione delle eccezioni del C++. –

0

Fuori della parte superiore della mia testa, si potrebbe essere in grado di realizzare qualcosa di simile con i segnali.

Impostare un gestore di segnale per la cattura di segnali appropriati e hanno essa le cose pulite in su. Ad esempio, se il tuo codice genera un SIGSEGV come risultato di qualcosa che altrimenti avrebbe gettato un'eccezione un po 'prima, puoi provare a prenderlo con il gestore del segnale.

Potrebbe esserci più di questo come non l'ho pensato.

Spero che questo aiuti.

+0

Non penso che tu voglia veramente prendere le violazioni di segmentazione. È del tutto possibile che l'heap o lo stack siano corrotti. – JeremyP

0

Chiamate eventuali librerie che potrebbero generare eccezioni? Se è il caso, avrai comunque bisogno di un tentativo di cattura. Per gli errori interni, ciascun metodo dovrà restituire un codice di errore (utilizzare return solo per il codice di errore, utilizzare i parametri di riferimento per restituire i valori effettivi). Se si desidera rendere la pulizia della memoria affidabile al 100%, è possibile avviare l'applicazione utilizzando un'applicazione di monitoraggio. Se l'applicazione si arresta in modo anomalo, il monitor lo riavvia. Hai ancora bisogno di chiudere i file e la connessione DB, tho.

6

Se non è possibile throw un'eccezione, quindi l'alternativa è quella di return (o return false o il codice di errore simile).

Sia che si passi o si ritorni, si utilizzano ancora distruttori deterministici in C++ per rilasciare risorse.

L'unica cosa che non si può facilmente "restituire" è un costruttore. Se hai un errore irreversibile in un costruttore, allora è un buon momento per lanciare; ma se non ti è permesso di lanciare, allora devi tornare, e in tal caso hai bisogno di un altro modo per segnalare il fallimento della costruzione:

  • avere costruttori privati ​​e metodi di fabbrica statici; avere il metodo factory return null in caso di fallimento della costruzione; non dimenticare di verificare un ritorno nullo quando chiami un metodo di fabbrica
  • Avere una proprietà "get_isConstructedOk()" che invochi dopo ogni costruttore (e non dimenticare di invocarla/controllarla su ogni oggetto di nuova costruzione)
  • attuare la costruzione 'due fasi': in cui si dice che qualsiasi codice che potrebbe non riuscire non deve essere in un costruttore, e devono invece essere in un metodo separato bool initialize() che si chiama dopo il costruttore (e non dimenticare chiamare initialize e non dimenticare di controllare il valore restituito).
4

Solo perché usare le eccezioni è vietato nei vostri standard di codifica attuali questo non significa che li si dovrebbe respingere di mano per futuri problemi si incontrano come questo. Potrebbe essere il caso che i tuoi attuali standard di codifica non prevedessero un tale scenario. Se lo facessero, probabilmente ti daranno un aiuto su quale sarebbe l'implementazione alternativa.

Questo mi sembra un buon momento per sfidare gli attuali standard di codifica. Se le persone che li hanno scritti sono ancora lì, parlateli direttamente perché saranno in grado di rispondere alla vostra domanda su strategie alternative o accetteranno che questo è un caso d'uso valido per le eccezioni.

+4

Potresti essere sorpreso, ma molte piattaforme non supportano nemmeno eccezioni ... quindi questa è una domanda legittima che merita una risposta costruttiva. –

+3

@kotlinski: Sto dicendo che il non uso delle eccezioni dovrebbe essere giustificato. Se la piattaforma di destinazione dell'OP non li supporta, questa è una buona giustificazione! Tuttavia, sospetto che l'OP lo avrebbe formulato in quel modo piuttosto che dire che le loro linee guida sulla codifica non le hanno permesse. – Troubadour

+0

Mi sembra una risposta costruttiva. Che succede se non prendono di mira nulla se non Win32, e gli sviluppatori originali erano vecchi e non volevano nessuno di questi affari delle nuove eccezioni a cui tutti i bambini stanno parlando in questi giorni? Poi di nuovo, potresti avere ragione, forse non tutte le piattaforme di destinazione supportano le eccezioni, o hanno problemi con il lancio di eccezioni tra i contorni delle DLL o quant'altro. Parlare con le persone che hanno sviluppato gli standard avrebbe sicuramente aiutato a spiegare perché i loro standard lo avrebbero detto. –

4

Tuttavia, l'uso di eccezioni è vietato nel nostro standard di codifica (anche in BTW standard C++ di Google). C'è un disegno alternativo che posso prendere in considerazione?

Gli standard di codifica sono pazzeschi.

Suggerisco di chiedere alla persona/alle persone che hanno sviluppato e imposto lo standard su come risolvere il problema. Se non hanno una buona risposta, usa questo come giustificazione per ignorare quella parte dello standard di codifica ... con il permesso dei padroni, ovviamente.

+0

Ma c'è una buona giustificazione! Ad esempio, Google menziona il fatto che la combinazione di codice senza eccezioni con codice che contiene eccezioni potrebbe essere problematica.Ad esempio, l'utilizzo di eccezioni ci costringerà a modificare le opzioni di compilazione per tutto il progetto e non sono sicuro che ne valga la pena. Grrr ... Non avrei dovuto menzionare lo standard del codice. Diviene la discussione dalle cose tecniche. –

+0

@Elazar - non vi è alcuna giustificazione per escludere a priori un costrutto che fa cose che sono troppo difficili da correggere altre in altri modi. Se le eccezioni vengono utilizzate correttamente, vanno bene. Lo standard dovrebbe concentrarsi sulla definizione di precondizioni/avvertimenti. –

4

Tuttavia, utilizzando eccezione è vietato nel nostro standard di codifica (anche in standard di Google C++ BTW). Esiste un progetto alternativo che posso prendere in considerazione?

Risposta breve è no.

Risposta lunga :). È possibile rendere tutte le funzioni restituire un codice di errore (simile all'implementazione della piattaforma COM di Microsoft.

I principali svantaggi di questo approccio sono:

  • si deve gestire tutti i casi eccezionali espressamente

  • tuoi dimensione aumenta codice drammaticamente

  • il codice diventa più difficile da leggere.

Invece di:

initialize(); 
while (true) { 
    Message msg,out; 
    recieve(msg); 
    process(msg,out); 
    //no global state is saved between loop iterations! 
    send(out); 
} 

si ha:

if(!succeedded(initialize())) 
    return SOME_ERROR; 

while (true) { 
    Message msg,out; 
    if(!succeeded(RetVal rv = recieve(msg))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
    if(!succeeded(RetVal rv = process(msg,out))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
    //no global state is saved between loop iterations! 
    if(!succeeded(RetVal rv = send(out))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
} 

Inoltre, l'attuazione tutte le funzioni dovrà fare lo stesso: circondare ogni chiamata di funzione con una if.

Nel precedente esempio, si hanno anche per decidere se il valore di rv ad ogni iterazione costituisce un errore per la funzione corrente e (eventualmente) di ritorno direttamente dal while, o rompere il mentre in caso di errori, e restituire il valore.

In breve, ad eccezione dell'eventuale utilizzo di RAII nel codice e nei modelli (è possibile utilizzarli?), Si finisce vicino a "codice C, utilizzando il compilatore C++".

Il codice trasforma ogni funzione da una a due-liner in un otto-liner e così via. È possibile migliorare questo con l'uso di funzioni extra e #define d macro, ma le macro hanno le proprie idiosincrasie che bisogna essere molto attenti circa.

In breve, i vostri standard di codifica stanno facendo il codice inutilmente più lungo, più soggetto a errori, più difficile da capire e più difficile da mantenere.

Questo è un buon esempio per presentare a chi è responsabile degli standard di codifica nella vostra azienda :(

Modifica: è anche possibile implementare questo con i segnali, ma sono un cattivo sostituto di eccezioni: essi fare la stessa cosa delle eccezioni, solo loro disabilitano completamente RAII e rendono il tuo codice ancora meno elegante e più incline agli errori

+0

Non è possibile fare in modo che i costruttori restituiscano un codice di errore: i costruttori non hanno codice di ritorno. – ChrisW

+1

No, non puoi. Almeno non direttamente. Puoi comunque fornire loro un argomento di ritorno. Questo rende il codice ancora più brutto per i costruttori. Disabilita anche gli operatori che sovraccaricano tra le altre cose. Questa è la ragione per cui ho detto che la risposta breve è ** no **. – utnapistim

+0

Un'opzione migliore potrebbe essere quella di evitare di scrivere costruttori che potrebbero fallire: usa invece una funzione init() separata che invece fa le cose pesanti. –

0

Un altro approccio è, invece di generare un'eccezione, impostare un indicatore di errore globale e restituire un input legale ma arbitrario. Quindi controllare ogni iterazione del ciclo indipendentemente dall'impostazione dell'indicatore di errore globale e, se lo è, restituire.

Se si presta attenzione, è possibile assicurarsi che la restituzione dei dati legali non causerà mai un arresto anomalo o un comportamento non definito. Pertanto non dovresti preoccuparti che il software continui a funzionare un po 'fino a raggiungere la condizione di controllo degli errori più vicina.

Per esempio

#define WHILE_R(cond,return_value) while (cond) {\ 
    if (exception_thrown) return return_value 
#define ENDWHILE() } 

bool isPolyLegal(Poly p) { 
    PolyIter it(p); 
    WHILE_R(it.next(),true) //return value is arbitary 
    ... 
     if (not_enough_memory) exception_thrown = true; 
    ... 
    ENDWHILE() 
}