2015-08-04 22 views
5

Non riesco proprio a capire come un utente sia in grado di distinguere tra le eccezioni che le mie funzioni possono generare. Una delle mie funzioni può generare due istanze di std::invalid_argument.Distinzione tra più eccezioni dello stesso tipo

Per esempio, in un costruttore:

#include <stdexcept> // std::invalid_argument 
#include <string> 

class Foo 
{ 
public: 
    void Foo(int hour, int minute) 
    :h(hour), m(minute) 
    { 
     if(hour < 0 || hour > 23) 
      throw std::invalid_argument(std::string("...")); 
     if(minute < 0 || minute > 59) 
      throw std::invalid_argument(std::string("...")); 
    } 
} 

Nota: E 'un esempio, si prega di non rispondere con numeri interi limitati.

Supponiamo che l'utente chiami con foo(23, 62);, in che modo il gestore delle eccezioni dell'utente distinguerebbe tra le due possibili istanze di std::invalid_argument?

O sto facendo questo sbagliato, e dovrei ereditare da std :: invalid_argument per distinguere tra loro? Cioè,

class InvalidHour: public std::invalid_argument 
{ 
public: 
    InvalidHour(const std::string& what_arg) 
    :std::invalid_argument(msg) {}; 
} 

class InvalidMinute: public std::invalid_argument 
{ 
public: 
    InvalidMinute(const std::string& what_arg) 
    :std::invalid_argument(msg) {}; 
} 

Poi, gettare InvalidHour e InvalidMinute?

Modifica: Creare una classe per ogni possibile eccezione mi sembra un po 'troppo, specialmente in un programma di grandi dimensioni. In questo modo, ogni programma che utilizza in modo efficace le eccezioni viene fornito con un'ampia documentazione su cosa catturare?

Come menzionato in una risposta, ho considerato assert su. Guardando attorno allo stackoverflow, ho trovato una maggioranza di persone che dicono che dovresti fare un'eccezione (come il mio caso particolare è per un costruttore ).

Dopo aver esaminato un sacco di informazioni online su quando utilizzare le eccezioni, il consenso generale è quello di utilizzare uno assert per errori logici ed eccezioni per errori di runtime. Sebbene chiamare foo(int, int) con argomenti non validi potrebbe essere essere un errore di runtime. Questo è ciò che voglio affrontare.

+4

* Idealmente * si dovrebbero avere due tipi, 'Ora' e' Minuto', che generano il proprio tipo di eccezione quando costruiti con valori non validi. – user657267

+0

Sì, sei corretto, dovresti introdurre nuovi tipi se vuoi distinguere tra loro in modo pulito –

+3

* "come un utente sarà in grado di distinguere tra le eccezioni che le mie funzioni possono generare" * ** Perché ** fai bisogno dell'utente/l'utente ha bisogno di distinguere tra questi due casi? 'Pippo' convalida solo i suoi argomenti? – dyp

risposta

4

La gerarchia di eccezioni standard non è adatta per errori logici. Usa uno assert e fallo con esso. Se vuoi assolutamente trasformare i bug più difficili in più per rilevare gli errori di run time, nota che ci sono solo due cose ragionevoli che un gestore può fare: raggiungere l'obiettivo contrattuale in un modo forse diverso (magari solo riprovando l'operazione), o in gira un'eccezione (di solito è solo un ripensamento), e che la causa esatta dell'eccezione originale raramente gioca un ruolo in questo. Infine, se vuoi supportare il codice che prova davvero varie combinazioni di argomenti finché non trova uno che non gira, non importa quanto stupido appare ora che è scritto a parole, beh, hai std::system_error per passare un errore intero codice su, ma è possibile può definire classi di eccezione derivata.

Tutto ciò detto, andare per il assert.

Ecco a cosa serve.

+0

Cosa succede se si verifica un errore di runtime? Una rescissione in quel caso non sarebbe giustificata, vero? –

+0

@ShreyasVinod: il termine "errore di runtime" di solito si riferisce a una gestione degli errori in fase di esecuzione, ma non è assolutamente possibile che un valore di argomento non valido sia quello. Quindi ho solo una vaga possibilità di ciò che intendi, ma penso che qualunque cosa sia, può aiutare a pensare a una funzione che ha un ** contratto **. Ha alcuni requisiti, ad es. sui valori degli argomenti, e se tali requisiti sono soddisfatti, allora è obbligato a produrre un determinato risultato o, se fallisce, a lanciare un'eccezione.In caso contrario, con una violazione del contratto la garanzia è annullata. È come un motore di un'auto: dagli il gas, OK, dagli l'acqua zuccherata,! –

+0

Stavo pensando più a qualcosa fuori dal controllo dell'utente. Forse un valore esterno fornito dall'utente finale che sembra essere sbagliato. Immagino sia responsabilità dell'utente della funzione assicurarsi che questo input sia valido prima di trasmetterlo alla mia funzione. Hai assolutamente ragione. –

1

È inoltre possibile creare ulteriori classi di errore che derivano da invalid_argument e che potrebbero renderle distinguibili, ma questa non è una soluzione scalabile. Se ciò che si vuole veramente è mostrare al lettore un messaggio che può capire, allora il parametro stringa su invalid_argument servirebbe a tale scopo.

1

Le eccezioni standard non consentono di memorizzare le informazioni aggiuntive desiderate e l'analisi dei messaggi di eccezione è una cattiva idea.Una soluzione è quella di sottoclasse, come dici tu. Ce ne sono altri - con l'avvento di std::exception_ptr. è possibile utilizzare le eccezioni "inner" (o "nested") come in Java o .NET, sebbene questa funzione sia più applicabile alla traduzione delle eccezioni. Alcuni preferiscono Boost.Exception, come un'altra soluzione per eccezioni estendibili in fase di runtime.

Non cadere nella "giusta asserzione" come Cheers e hth. Semplice esempio:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen) 
{ 
    assert(fromLen <= bufLen); 
    std::copy(from, from + fromLen, buf); 
} 

Non c'è niente di sbagliato con il assert per sé, ma se il codice viene compilato per il rilascio (con NDEBUG set), quindi safe_copy non sarà al sicuro a tutti, e il risultato può essere un sovraccarico del buffer , potenzialmente consentendo a una parte malintenzionata di assumere il controllo del processo. Lanciare un'eccezione per indicare un errore logico ha i suoi problemi, come detto, ma almeno impedirà il comportamento non definito immediato nella build di rilascio. Vorrei quindi suggerire, in funzioni di sicurezza critiche, per usare le asserzioni di debug, e le eccezioni nella build di rilascio:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen) 
{ 
    assert(fromLen <= bufLen); 
    if (fromLen > bufLen) 
     throw std::invalid_argument("safe_copy: fromLen greater than bufLen"); 
    std::copy(from, from + fromLen, buf); 
} 

Naturalmente, se si utilizza questo modello molto, si potrebbe desiderare di definire un una tua macro per semplificare il compito. Questo però va oltre lo scopo del tema attuale. altri

+0

Una volta (come 15 anni fa) ho risolto la soluzione che presentavi, una combo con assert e lancio di eccezioni, e pubblicato un articolo su di esso sul web (oggi sarebbe un post sul blog). Per essere un po 'meno pericoloso, un'eccezione difficile che si propaga in alto, l'eccezione che prende il posto di un 'assert'non deve essere un'eccezione standard. Si è scoperto che non ho usato la cosa più tardi. Nessun altro ha fatto nessuno dei due. È come l'idea di definire una macro 'WITH' (anche BTDT). Quindi penso che il tuo licenziamento del mio consiglio apparentemente semplice sia un po 'prematuro. Si basa su una volta che hai inventato la tua soluzione. –

+1

Hm, mentre ci sono, mi piacerebbe notare che il controllo della validità degli argomenti che non controlla valori enormi di argomenti interi senza segno, è peggio di nessun controllo. Fornisce un falso senso di sicurezza pur non controllando l'errore più comune (avvolgimento di valore negativo). Se si desidera assolutamente l'intervallo di 64 bit possibilmente grande per i build a 64 bit e si desidera un nome descrittivo, allora l'uso di Size = ptrdiff_t' è tuo amico. –

1

Due motivi per generare eccezioni, piuttosto che avere asserzioni è quando si implementa una libreria o una qualche forma di codice esportabile e non può dire priori modo in cui un utente vorrà gestire una qualche forma di errore o quando è necessario il controllo quando l'utente crea il tuo codice in modalità RELEASE (cosa che gli utenti spesso fanno). Nota che costruire in modalità RELEASE "toglie" qualsiasi asserzione.

Per esempio, dare un'occhiata a questo codice:

struct Node 
{ 
    int data; 
    Node* next; 
    Node(int d) : data(d), next(nullptr) {} 
}; 

// some code 
Node* n = new Node(5); 
assert(n && "Nodes can't be null"); 

// use n 

Quando questo codice è costruito in modalità di rilascio, che l'affermazione "non esiste" e il chiamante potrebbe ottenere n essere nullptr in fase di esecuzione.

Se il codice genera un'eccezione anziché l'asserzione, il chiamante può ancora "reagire" a un'anomalia nullptr in entrambe le build di debug e release. Il rovescio della medaglia è che l'approccio di eccezione richiede molto più codice della piastra della caldaia.

Problemi correlati