Opzione di codici di errore nella libreria C ++ per le prestazioni

4

Ho scritto un open source e multipiattaforma Libreria di file C ++ che hanno codici di eccezione e di errore. Le eccezioni possono essere disabilitate quando il programma è in esecuzione. In tal caso, l'utente deve controllare i codici di errore. Ovviamente, quando il lancio delle eccezioni è abilitato, il codice di errore non verrà restituito perché viene generata un'eccezione prima che la funzione richiamata ritorni.

Attualmente, le eccezioni possono ancora essere generate quando disabilitate (bug). Lo aggiusterò presto. Qualcuno mi commenta che è considerato un cattivo progetto avere sia codici di eccezione che di errore. Sono d'accordo con lui e sto per rimuovere i codici di errore e fare in modo che la libreria generi eccezioni derivate personalizzate con ulteriori informazioni di errore.

Ma esito. Mi piace avere questo approccio ibrido per motivi di prestazioni. Questo è quello che sto pensando di fare: mantenere i codici di errore e di eccezione, ma il lancio delle eccezioni è disabilitato attraverso la macro in fase di compilazione invece che in runtime.

Se l'utente definisce quanto segue in un file di configurazione comune

#define ELMAX_EXCEPTION 1

Il codice seguente

int Write(std::wstring str) ELMAX_THROW_OR_NOT;

si espanderebbe in uno dei 2.

// under C++11 compiler
int Write(std::wstring str) noexcept;

// under C++98 compiler
int Write(std::wstring str) throw();

Se ELMAX_EXCEPTION non è definito o è zero, la macro si espanderebbe a zero.

int Write(std::wstring) ;

Il motivo per cui voglio farlo, è per l'utente della biblioteca che vuole il guadagno di prestazioni di non avere il compilatore che genera il codice di sbobinamento dello stack (per eccezioni) e il compilatore può ottimizzare meglio tali funzioni. La mia libreria di file C ++ utilizza l'API del file C e l'unica cosa che potrebbe generare un'eccezione è new keyword, che intendo aggiungere nothrow . Il codice UTF-8 può anche generare eccezioni (è necessario modificarle anche)

char *parr = new (nothrow) char[num];

if(parr==NULL)
    // handle it either through error code or throw exception (if enabled)

Si prega gentilmente di consigliare se dovrei avere un approccio di eccezione pura o un approccio ibrido (come detto), per i miei errori di libreria?

    
posta Shao Voon Wong 30.12.2012 - 02:33
fonte

4 risposte

8

Il costo del lancio di un'eccezione è trascurabile rispetto alla scrittura su disco. Inoltre, i codici di ritorno sono più costosi, non meno. Alla fine, a nessuno importa quanto sia veloce la tua biblioteca quando non riescono a far sì che il loro programma abbia la semantica corretta perché il loro compagno di squadra che si è appena unito alla compagnia ha dimenticato di controllare un codice di ritorno.

L'unico codice di ritorno sicuro è quello che l'utente può tranquillamente ignorare.

    
risposta data 30.12.2012 - 14:05
fonte
2

La scelta tra le eccezioni e i codici di errore dovrebbe not essere globale, ma piuttosto basata su quanto eccezionale sia la condizione di errore con un normale utilizzo della libreria.

  • I codici di errore devono essere utilizzati per situazioni errate, ma che possono facilmente verificarsi con un normale utilizzo della libreria. Ad esempio, mancata apertura di un file o mancata lettura di un intero nella posizione corrente.
  • Si dovrebbero usare eccezioni per segnalare problemi gravi che non dovrebbero verificarsi con l'uso normale della libreria e che è improbabile che il chiamante immediato sia in grado di recuperare. Esempi di questi sono l'esaurimento della memoria o gli errori gravi riportati dal file system sottostante (ad esempio, il disco pieno).

E per le librerie I / O, puoi anche considerare l'utilizzo di un meccanismo di segnalazione degli errori ritardati. Invece di far restituire ad ogni funzione un codice di errore (che deve essere controllato dal chiamante), il codice di errore viene memorizzato internamente. Le chiamate successive non eseguono nulla se determinano che una chiamata precedente ha generato un errore e l'utente della libreria deve verificare (in un punto opportuno) se si è verificato un errore nel gruppo di chiamate precedente. Questa è la modalità di funzionamento predefinita per i flussi di I / O standard C ++.

    
risposta data 30.12.2012 - 13:36
fonte
1

Se hai buoni casi d'uso per entrambi, prendi in considerazione di dividere la tua biblioteca in due o tre classi:

  • Il core, che contiene il codice che fa il lavoro ed è in grado di restituire informazioni sufficienti in caso di errore nel produrre un codice di errore o un'eccezione.
  • Una classe wrapper che chiama i metodi del core e genera eccezioni in caso di errore. Il compromesso qui è che le eccezioni generate non mostreranno esattamente dove si è verificato un errore nella classe principale. Questo potrebbe non essere un problema a seconda della complessità dei metodi del core.
  • Una classe wrapper che chiama i metodi del core e restituisce i codici di errore in caso di errore. Se appropriato, la classe principale potrebbe essere utilizzata direttamente, eliminando la necessità di disporre di un wrapper senza lancio.

Qualunque cosa tu faccia, non cambiare il comportamento della biblioteca usando il preprocessore. Così facendo cambierai i prototipi del metodo:

class Foo {
  #ifdef NO_EXCEPTIONS
    StatusType bar() { ... }
  #else
    void bar() { ... }
  #endif
};

La prima volta che una classe aspetta il comportamento di eccezione #include s l'altra intestazione che ha il macro set NO_EXCEPTIONS , avrai codice non compilabile perché ci sarà un codice presente che si aspetta Foo da prototipare due modi diversi.

    
risposta data 30.12.2012 - 13:23
fonte
1

Dipende da te, ma i codici di errore sono più facili da documentare e gestire all'interno di una libreria come questa. Le eccezioni possono essere un killer delle prestazioni, se le utilizzi per tutti i possibili errori allora potresti finire con il loro utilizzo per il flusso del programma - e questo è molto brutto. Inoltre, sarei preoccupato che qualcuno stia chiamando la tua libreria da un'app con la gestione delle eccezioni disattivata.

In generale, se stai scrivendo un componente completamente sigillato, non dovresti creare eccezioni al di fuori di esso a meno che non siano documentate in modo definitivo come aspettarsi. Nessuno vuole sapere quali sono i componenti interni del componente, quindi tutte le eccezioni generate internamente devono essere catturate e quindi (ove applicabile) presentate all'utente come un caso di errore definito. Nella maggior parte dei casi, la restituzione di queste informazioni come codice di errore è l'approccio più semplice, più semplice e più sicuro.

Il motivo qui è che puoi lanciare qualsiasi eccezione che ti piace, oppure il sistema può farlo per te, e l'utente del tuo componente non può farci niente. Almeno con i codici di errore, non lascerai propagare eccezioni non gestite all'utente.

Non devi consentire alle eccezioni di tornare dal tuo componente se sono errori "normali". Le persone dicono che le eccezioni dovrebbero essere per situazioni eccezionali e hanno ragione - una volta che inizi a restituire le eccezioni di "fine del file", l'utente dovrà racchiudere tutte le ultime chiamate del metodo in un blocco try - il che rende il codice veramente disordinato . Se usi le eccezioni, l'utente dovrebbe essere in grado di chiamare le tue funzioni e non mettere mai un blocco try ovunque, se niente va storto non ci sarà alcun problema. Se non riesci a raggiungere questo obiettivo, il codice dell'utente dovrà essere disseminato di gestori di eccezioni per tutti i casi in cui pensa che potrebbe lanciare uno.

Un approccio ibrido può funzionare anche se , codici di errore per la maggior parte delle cose con eccezioni per tutti i problemi imprevisti, ma se lo fai, potresti anche rimani con i soli codici di errore ai confini della tua biblioteca.

    
risposta data 30.12.2012 - 13:37
fonte

Leggi altre domande sui tag