Penso che il modo più semplice per risolverlo sia quello di avere la funzione di analisi dell'argomento della riga di comando (chiamiamola parse_cmd_args
) gestire l'opzione --help
stessa. Nella più semplice implementazione, stampa semplicemente il messaggio di aiuto e quindi chiama exit(0)
.
Ovviamente, questa non è una separazione netta tra preoccupazioni e test unitari difficili, quindi potresti voler fare meglio.
Per prima cosa, invece di stampare direttamente sullo standard output, puoi fare in modo che parse_cmd_args
accetti un argomento aggiuntivo di tipo std::ostream
e utilizzalo per visualizzare il testo della guida. Nel tuo test di unità, puoi passare un std::ostringstream
anziché std::cout
.
La prossima cosa da eliminare è chiamare exit
direttamente da parse_cmd_args
. A mio avviso, abbiamo tre possibili casi.
- L'analisi degli argomenti della riga di comando ha esito positivo e i valori analizzati sono memorizzati in un oggetto
options
per ulteriore utilizzo da parte del programma.
- L'analisi delle opzioni della riga di comando non riesce. Il programma dovrebbe stampare un messaggio di errore e uscire immediatamente con uno stato che indica un errore.
- È stata rilevata l'opzione
--help
(o --version
). Il programma dovrebbe stampare il testo corrispondente e quindi uscire immediatamente con uno stato che indica il successo.
Attualmente stai gestendo il caso 1 attraverso il normale return
, il caso 2 tramite throw
un'eccezione e sei indeciso per il caso 3. Concordo sul fatto che la gestione del caso 3 con un'eccezione non sia il modo più elegante (anche se non obiettare troppo contro di esso). D'altra parte, penso che i casi 2 e 3 siano abbastanza simili e dovrebbero essere gestiti allo stesso modo.
Siccome "noi" abbiamo già deciso di fare parse_cmd_args
di stampare il testo di aiuto nel caso 3, potremmo anche farlo stampare il messaggio di errore nel caso 2. Poiché il messaggio di errore dovrebbe andare all'output dell'errore standard, dovremmo aggiungere un secondo argomento std::ostream
per quello.
Ora che parse_cmd_args
gestisce internamente tutta la stampa, tutto ciò che deve comunicare al chiamante è se uscire immediatamente e, in caso affermativo, con quale stato. Poiché si esce immediatamente e dopo aver analizzato options
sono ortogonali tra loro, si può usare una variante per questo.
std::variant<options, int>
parse_cmd_args(int argc, char * * argv, std::ostream& out, std::ostream& err);
Purtroppo, std::variant
sarà disponibile solo in C ++ 17, quindi potresti dover utilizzare boost::variant
per il momento. Se questo sembra eccessivo, c'è sempre l'opzione pragmatica (se forse meno elegante) per usare options
come parametro di uscita e return
lo stato di uscita, inventando alcune convenzioni (come return
ing un valore negativo) per indicare "Non uscire".
Un secondo pensiero è gestire l'opzione --help
in una funzione separata (chiamiamola parse_cmd_special
) che main
chiama prima di chiamare parse_cmd_args
. Si finirebbe con qualcosa di simile.
// Returns true if and only if the help message was printed and the program
// should exit without any further argument processing.
bool
parse_cmd_special(int argc, char * * argv, std::ostream& out);
// Communicates errors through exceptions.
options
parse_cmd_args(int argc, char * * argv);
Come ultima osservazione, la prima cosa che mi piace fare nelle mie funzioni main
è di comprimere tutti gli argomenti da riga di comando da argv[1]
a argv[argc - 1]
in std::vector<std::string>
e passarlo per qualsiasi ulteriore elaborazione . A meno che tu non abbia letteralmente migliaia di argomenti da riga di comando, il sovraccarico non dovrebbe essere evidente e le tue interfacce di funzione (considerate le tre mostrate sopra) diventano molto più pulite.