Per affinare le mie capacità - e per il piacere di farlo - sto scrivendo un piccolo gioco per i miei figli nel moderno C ++ (C ++ 11, C ++ 14 e la parte di C ++ 17 già supportata da Visual Studio ), che è una bella pausa dai miei soliti compiti di programmazione (aziendale).
Ho bisogno di un piccolo interprete per l'input dell'utente e ovviamente devo gestire input errati (pensate a "missile magico csat" invece di "lanciare missili magici"). Questo non è eccezionale, piuttosto è la norma che l'input dell'utente può essere per qualche motivo non ben formato.
Quindi sto cercando l'approccio raccomandato per gestirlo. Ho letto le Linee guida per C ++ o altre domande sul sito, e ho sperimentato vari approcci nel mio codice.
Alla fine ho deciso di cambiare tutti i tipi di ritorno rilevanti in qualcosa di simile
std::tuple<my_true_return_type, RetCode> my_function(...);
dove RetCode è un enum:
enum RetCode{
SUCCESS,
WRONG_NUMBER_OF_INPUTS,
....
};
e ogni chiamata di funzione è fatta come:
std::tie(result, error_code) = my_function(...);
Questo sembra più o meno in linea con le linee guida e posso essere piuttosto sistematico con esso.
Il mio unico problema con questa soluzione è che può capitare di essere in grado di rilevare un errore prima ancora di costruire my_true_return_type e in questo caso devo costruirlo in ogni caso, solo per scartarlo nel punto di chiamata.
Qualcosa di simile
if (an_erroneous_condition){
// I have to default-construct a return type object,
// which I didn't really need
return std::make_tupe(my_true_return_type(), FAILURE);
}
Potrei invece optare per un tipo di ritorno Nullable (come la Forse monad in Haskell), ma a un certo costo.
In tal caso, avrei un tipo di ritorno relativamente complesso:
std::tuple<SomeNullableType<my_true_return_type, ...>, RetCode>
e la complessità risultante nel punto di chiamata. Inoltre, non esiste ancora un tipo annullabile nella libreria standard AFAIK , o in Boost (lì è stata la proposta di Boost.Outcome ma è ancora in fase di evoluzione), quindi avrei bisogno di trovare un'altra soluzione, o rollare la mia che è anche divertente, ma non sembra strettamente necessario allo scopo di codificare un semplice gioco. E con ogni probabilità la mia soluzione sarebbe mezza cotta, non pronta per la produzione.
Se torno alla route di eccezioni ottengo un codice di ritorno semplificato e non devo costruire nessun oggetto di cui non ho bisogno, ma mi sembra che le eccezioni siano riservato per casi eccezionali e non per quello che mi aspetto essere molto comune.
Quindi la mia domanda è:
- come gestire, secondo le migliori pratiche attuali e sfruttando appieno tutta la moderna tecnica C ++, errori che non sono situazioni eccezionali?
- se il mio modo di procedere è ragionevole, cosa posso fare per garantire che il costo del tipo di rendimento "predefinito" "inutile" sia ridotto al minimo?