2012-09-03 14 views
11

Ho recentemente iniziato a usare boost::program_options e l'ho trovato molto conveniente. Detto questo, manca una cosa che non sono riuscito a codificarmi in modo corretto:boost :: program_options: iterando su e stampando tutte le opzioni

Vorrei ripetere tutte le opzioni che sono state raccolte in un boost::program_options::variables_map per stamparle sullo schermo. Questa dovrebbe diventare una funzione di convenienza, che posso semplicemente chiamare per elencare tutte le opzioni che sono state impostate senza la necessità di aggiornare la funzione quando aggiungo nuove opzioni o per ciascun programma.

So che posso controllare ed emettere singole opzioni, ma come detto sopra, questa dovrebbe diventare una soluzione generale che è ignara delle opzioni effettive. So anche che posso scorrere il contenuto di variables_map poiché è semplicemente un esteso std::map. Potrei quindi verificare il tipo containd nella variabile boost::any memorizzata e utilizzare .as<> per riconvertirlo nel tipo appropriato. Ma ciò significherebbe codificare un lungo blocco di commutazione con un caso per ciascun tipo. E questo non mi sembra un buon stile di programmazione.

Quindi la domanda è, c'è un modo migliore per scorrere su queste opzioni e produrle?

risposta

5

È consigliabile utilizzare il pattern Visitor. Sfortunatamente boost::any non supporta il pattern Visitor come boost::variant. Tuttavia ci sono alcuni terzi approaches.

Un'altra possibile idea è utilizzare RTTI: creare la mappa di type_info di tipi noti associati al tipo gestore di funzioni.

+0

Grazie per il link e l'idea di RTTI . Speravo che potessi evitare di costruire una struttura per tutti i tipi supportati che avrei dovuto gestire se i tipi aumentassero, ma sembra che ciò non sia possibile. Fondamentalmente, volevo passare il dollaro ai tipi - come se supportassero 'operatore <<' tutto funziona bene, altrimenti la compilazione dovrebbe fallire. – shiin

5

Come @Rost precedentemente menzionato, il pattern Visitor è una buona scelta qui. Per utilizzarlo con l'ordine di acquisto è necessario utilizzare i notificanti per le opzioni in modo tale che, se l'opzione viene passata, il notificatore riempirà una voce nel proprio insieme di valori boost::variant. Il set dovrebbe essere memorizzato separatamente. Successivamente è possibile eseguire iterazioni sul set e elaborare automaticamente le azioni (ad esempio stampa) su di esse utilizzando boost::apply_visitor.

Per i visitatori, ereditano da boost::static_visitor<>

In realtà, ho fatto visitatore e l'uso approccio generico più ampio.

Ho creato un class MyOption che contiene descrizione, boost::variant per il valore e altre opzioni come implicite, predefinite e così via. Riempio un vettore di oggetti del tipo MyOption nello stesso modo come per le opzioni OP (vedere boost::po::options_add()) tramite i modelli. Nel momento in cui si passa std::string() o double() per l'inizializzazione boosts::varian t si riempi il tipo di valore e altre cose come predefinito, implicito.

Successivamente ho utilizzato il pattern Visitor per riempire il contenitore boost::po::options_description poiché boost::po ha bisogno delle proprie strutture per analizzare la riga di comando di input. Durante il riempimento ho impostato il server di notifica per ogni opzione - se sarà passata, boost::po riempirà automaticamente il mio oggetto originale di MyOption.

Successivamente è necessario eseguire po::parse e po::notify. Dopodiché sarai in grado di utilizzare già riempito std::vector<MyOption*> tramite il pattern Visitor poiché contiene boost :: variant all'interno.

Cosa c'è di buono in tutto questo - devi scrivere il tuo tipo di opzione solo una volta nel codice - quando riempi il tuo std::vector<MyOption*>.

PS. se si utilizza questo approccio si dovrà affrontare un problema di impostazione di notifica per un'opzione senza valore, fare riferimento a questo argomento per ottenere una soluzione: boost-program-options: notifier for options with no value

PS2. Esempio di codice:

std::vector<MyOptionDef> options; 
OptionsEasyAdd(options) 
    ("opt1", double(), "description1") 
    ("opt2", std::string(), "description2") 
... 
; 
po::options_descripton boost_descriptions; 
AddDescriptionAndNotifyerForBoostVisitor add_decr_visitor(boost_descriptions); 
// here all notifiers will be set automatically for correct work with each options' value type 
for_each(options.begin(), options.end(), boost::apply_visitor(add_descr_visitor)); 
+0

Grazie per la spiegazione approfondita. Questa è anche una soluzione interessante. In questo modo potrei anche aggiungere facilmente descrizioni diverse per l'output della guida e semplicemente elencando i valori delle opzioni. – shiin

0

Mi stavo occupando proprio di questo tipo di problema oggi. Questa è una vecchia domanda, ma forse questo aiuterà le persone che cercano una risposta.

Il metodo che ho trovato è di provare un po 'di < ...>() e quindi ignorare l'eccezione. Non è terribilmente carino, ma l'ho fatto funzionare.

Nel blocco di codice sottostante, vm è una variabile_map di boost program_options. vit è un iteratore su vm, che lo rende una coppia di std :: string e boost :: program_options :: variable_value, essendo quest'ultimo un boost :: any. Posso stampare il nome della variabile con vit-> first, ma vit-> second non è così facile da produrre perché è un boost :: any, cioè il tipo originale è stato perso. Alcuni dovrebbero essere lanciati come std :: string, alcuni come double e così via.

Così, a cout il valore della variabile, posso usare questo:

std::cout << vit->first << "="; 
try { std::cout << vit->second.as<double>() << std::endl; 
} catch(...) {/* do nothing */ } 
try { std::cout << vit->second.as<int>() << std::endl; 
} catch(...) {/* do nothing */ } 
try { std::cout << vit->second.as<std::string>() << std::endl; 
} catch(...) {/* do nothing */ } 
try { std::cout << vit->second.as<bool>() << std::endl; 
} catch(...) {/* do nothing */ } 

ho solo 4 tipi che uso per ottenere informazioni dal file riga di comando/config, se ho aggiunto più tipi, dovrei aggiungere più linee. Ammetto che questo è un po 'brutto.

0

Dal momento che è sufficiente stamparli, è possibile acquisire la rappresentazione di stringa originale durante l'analisi. (Probabilmente ci sono errori di compilazione nel codice, ho strappato dalla mia base di codice e il mazzo non-typedefed delle cose)

std::vector<std::string> GetArgumentList(const std::vector<boost::program_options::option>& raw) 
{ 
    std::vector<std::string> args; 

    BOOST_FOREACH(const boost::program_options::option& option, raw) 
    { 
     if(option.unregistered) continue; // Skipping unknown options 

     if(option.value.empty()) 
      args.push_back("--" + option.string_key)); 
     else 
     { 
      // this loses order of positional options 
      BOOST_FOREACH(const std::string& value, option.value) 
      { 
       args.push_back("--" + option.string_key)); 
       args.push_back(value); 
      } 
     } 
    } 

    return args; 
} 

Usage:

boost::program_options::parsed_options parsed = boost::program_options::command_line_parser(... 

std::vector<std::string> arguments = GetArgumentList(parsed.options); 
// print 
Problemi correlati