In primo luogo, come altri hanno affermato, le cose non sono così chiare in C ++, IMHO principalmente perché i requisiti e le restrizioni sono in qualche modo più vari in C ++ rispetto ad altre lingue, esp. C # e Java, che hanno problemi di eccezione "simili".
Esporrò sull'esempio std :: stof:
passare una stringa vuota a std :: stof (genererà invalid_argument) non un errore di programmazione
Il contratto di base , a mio modo di vedere, di questa funzione è che tenta di convertire il suo argomento in un float e qualsiasi errore nel farlo viene segnalato da un'eccezione. Entrambe le possibili eccezioni derivano logic_error
ma non nel senso di errore del programmatore, ma nel senso di "l'input non può, mai, essere convertito in un float".
Qui, si potrebbe dire che logic_error
viene usato a per indicare che, dato quell'input (di runtime), è sempre un errore tentare di convertirlo - ma è compito della funzione determinarlo e dirlo (tramite eccezione).
Nota a margine: in quella vista, a runtime_error
potrebbe essere visto come qualcosa che, dato lo stesso input per una funzione, potrebbe teoricamente avere successo per diverse esecuzioni. (ad es. un'operazione sul file, accesso al DB, ecc.)
Nota a margine: la libreria di regex C ++ ha scelto di derivarne l'errore runtime_error
sebbene ci siano casi in cui potrebbe essere classificata come qui (modello regex non valido).
Questo dimostra, IMHO, che il raggruppamento logic_
o l' runtime_
errore è piuttosto confuso in C ++ e non aiuta molto nel caso generale (*) - se hai bisogno di gestire errori specifici, probabilmente devi catturare più in basso dei due.
(*): Questo non vuol dire che un singolo pezzo di codice non deve essere coerente, ma se si gettano runtime_
o logic_
o custom_
quarantina non è davvero così importante, credo.
Per commentare entrambi stof
e bitset
:
Entrambe le funzioni prendono le stringhe come argomento, e in entrambi i casi è:
- non banale controllare se il chiamante è valido se una determinata stringa è valida (ad esempio, nel caso peggiore dovresti replicare la logica della funzione; nel caso di bitset, non è immediatamente chiaro se la stringa vuota è valida, quindi lascia che il ctor decida)
- È già responsabilità della funzione "analizzare" la stringa, quindi deve già convalidare la stringa, quindi ha senso che segnala un errore per "utilizzare" la stringa in modo uniforme (e in entrambi i casi si tratta di un'eccezione) .
La regola che si presenta frequentemente con eccezioni è "usa eccezioni solo in circostanze eccezionali". Ma come può una funzione di biblioteca sapere quali circostanze sono eccezionali?
Questa affermazione ha, IMHO, due radici:
Prestazioni : se una funzione viene chiamata in un percorso critico e il caso "eccezionale" non è eccezionale, vale a dire che una quantità significativa di passaggi implicherà il lancio di un'eccezione, quindi pagare ogni volta per il meccanismo di svolgimento delle eccezioni non ha senso e potrebbe essere troppo lento.
Frazione di gestione degli errori : Se una funzione viene richiamata e l'eccezione viene immediatamente catturato e processato, allora c'è poco senso un'eccezione, la gestione degli errori sarà più dettagliato con il catch
che con un if
.
Esempio:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Ecco dove entrano in gioco funzioni come TryParse
vs Parse
.: Una versione per quando il codice locale si aspetta che la stringa analizzata sia valida, una versione quando il codice locale presuppone che ci si aspetta effettivamente (cioè non eccezionale) che l'analisi fallisca.
In effetti, stof
è solo (definito come) un wrapper strtof
, quindi se non vuoi eccezioni, usa quello.
Quindi, come dovrei decidere se dovrei usare le eccezioni o no per una particolare funzione?
IMHO, hai due casi:
Funzione simile a "libreria" (riutilizzata spesso in contesti diversi): praticamente non puoi decidere. Forse fornire entrambe le versioni, forse una che riporta un errore e una a capo che converte l'errore restituito in un'eccezione.
Funzione "Applicazione" (specifica per un BLOB di codice applicazione, può essere riutilizzata, ma è vincolata dallo stile di gestione degli errori delle app, ecc.): Qui, dovrebbe essere spesso abbastanza chiara. Se i percorsi di codice che chiamano le funzioni gestiscono le eccezioni in modo sano e utile, utilizzare le eccezioni per segnalare qualsiasi errore (ma vedere di seguito) . Se il codice dell'applicazione è più facile da leggere e scrivere per uno stile di ritorno dell'errore, utilizzarlo in ogni caso.
Ovviamente ci saranno posti in mezzo: basta usare ciò di cui hai bisogno e ricordare YAGNI.
Infine, penso che dovrei tornare alla dichiarazione FAQ,
Non usare il lancio per indicare un errore di codifica nell'uso di una funzione. Usa assert o altro meccanismo per inviare il processo in un debugger o per arrestarlo in modo anomalo ...
Sottoscrivo questo per tutti gli errori che indicano chiaramente che qualcosa è gravemente incasinato o che il codice chiamante chiaramente non sapeva cosa stesse facendo.
Ma quando questo è appropriato è spesso altamente specifico per l'applicazione, quindi vedi sopra il dominio della libreria rispetto al dominio dell'applicazione.
Questo ricade sulla domanda su se e come convalidare le precondizioni di chiamata , ma non entrerò in quello, risposta già troppo a lungo :-)