Come segnalare l'esistenza di un flag di opzione "--help"?

1

Ho una piccola funzione che analizza i miei argomenti della riga di comando. Verifica anche le opzioni richieste e genera eventuali errori. La funzione attualmente richiede argc e argv e restituisce una struttura che è formata in questo modo:

struct options
{
  std::string arg1;
  std::uint16_t arg2;
  std::any<std::string> not_required;
};

Eventuali errori del parser sono segnalati da eccezioni.

Ora volevo aggiungere un interruttore --help . Ma come lo implemento correttamente? Ho pensato a 3 possibili modi, vale a dire:

  1. Lancio di un'eccezione
  2. Aggiunta di un campo std::any<std::string> che viene popolato da un messaggio di aiuto

Non mi piace la prima opzione a causa dell'uso di eccezioni come metodo di controllo del flusso e la seconda opzione ha il problema, che avrei bisogno di contrassegnare ogni campo come facoltativo a causa della natura del --help switch, che può essere un'opzione autonoma.

    
posta Vincent 21.09.2016 - 20:21
fonte

1 risposta

1

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.

  1. 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.
  2. 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.
  3. È 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.

    
risposta data 22.09.2016 - 02:15
fonte

Leggi altre domande sui tag