2016-09-17 6 views
4

Предположим, что у меня есть основная функция, которая в основном просто вызывает еще одну функцию в качестве точки входа в программу. Функция (и, таким образом, полная программа) имеет ряд обязательных и ряд дополнительных параметров:Обработка необязательных параметров в главном

#include <iostream> 
#include <sstream> 

void function_to_call(std::string arg1, 
        std::string arg2, 
        std::string arg3, 
        std::string arg4, 
        std::string arg5 = "foo", 
        std::string arg6 = "bar", 
        int num1 = 1, 
        int num2 = 2 
       ) 

{ 
    // do fancy stuff here                                                                         
} 


int main(int argc, char** argv) 
{ 

    int num1, num2; 

    std::stringstream stream; 

    if(argc < 5) { 
    std::cerr << "Usage: \n\t" << argv[0] 
       << "\n\t\t1st argument" 
       << "\n\t\t2nd argument" 
       << "\n\t\t3rd argument" 
       << "\n\t\t4th argument" 
       << "\n\t\t5th argument (optional)" 
       << "\n\t\t6th argument (optional)" 
       << "\n\t\t7th argument (optional)" 
       << "\n\t\t8th argument (optional)" 
       << "\n\t\t9th argument (optional)" << std::endl; 
    } 
    if(argc == 5) { 
    function_to_call(argv[1], argv[2], argv[3], argv[4]); 
    } 
    if(argc == 6) { 
    function_to_call(argv[1], argv[2], argv[3], argv[4], argv[5]); 
    } 
    if(argc == 7) { 
    function_to_call(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); 
    } 
    if(argc == 8) { 
    stream << argv[7]; 
    stream >> num1; 
    function_to_call(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], num1); 
    } 
    if(argc == 9) { 
    stream << argv[7] << ' ' << argv[8]; 
    stream >> num1 >> num2; 
    function_to_call(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], num1, num2); 
    } 

    return 0; 

} 

if цепь может быть может быть заменен с switch, командная строка может быть убрано немного с помощью библиотеку getopt или повысить program_options, но это на самом деле ничего не меняет.

Есть ли очевидный способ, которым я не могу обрабатывать различные количества параметров?

+0

Первое, что приходит на ум - это цикл и std :: vector '. –

+0

Вы должны рассмотреть реализацию 'get options' (возможно, gnu getopt или boost :: options) –

+0

Или иметь локальные переменные, которые содержат значения по умолчанию для пропущенных параметров (и будут изменены эти параметры переданы), тогда вы требуется только вызов функции. – 1201ProgramAlarm

ответ

0

Я понимаю, что одним из способов сделать это может быть использование вектора (тривиального) базового класса, а затем небольшие специализации для разных типов, например. как это (не обязательно оптимальное):

class Base 
{ 
public: 
    virtual void set(const char *) = 0; 
}; 

class Int : public Base { 
public: 
    Int(int value) : value_(value) {} 
    Int(const char* value) : value_(std::stoi(value)) {} 
    virtual void set(const char* value) { value_ = std::stoi(value); } 
    int get() { return value_; } 
private: 
    int value_; 
}; 

class Str : public Base { 
public: 
    Str(const char* value): value_(value) {} 
    virtual void set(const char* value) { value_ = value; } 
    std::string get() { return value_; } 
private: 
    std::string value_; 
}; 

Тогда вариант разбора может быть сделано, как это, то есть фигура компилятора, какой тип мы имеем дело с

int main(int argc, char** argv) 
{ 

    std::vector<Base*> theopts = { new Str(""),new Str(""),new Str(""),new Str(""),new Str("foo"),new Str("bar"),new Int(1),new Int(2) }; 

    if(argc < 5) { 
    // put meaningful handling here 
    } 

    for(int i = 0; i < argc-1; ++i) { 
    theopts[i]->set(argv[i+1]); 
    } 

    function_to_call(static_cast<Str*>(theopts[0])->get(), 
        static_cast<Str*>(theopts[1])->get(), 
        static_cast<Str*>(theopts[2])->get(), 
        static_cast<Str*>(theopts[3])->get(), 
        static_cast<Str*>(theopts[4])->get(), 
        static_cast<Str*>(theopts[5])->get(), 
        static_cast<Int*>(theopts[6])->get(), 
        static_cast<Int*>(theopts[7])->get() 
        ); 
} 

вызов функции, то, очевидно, немного уродливым из-за явного каста, но число явных if s очень мало в этой реализации.

2

Boost program options - это библиотека, которая может быть очень полезна при анализе входных аргументов. В частности, аргументы могут быть указаны для принятия значений по умолчанию, если они не указаны в командной строке. Если аргументы по умолчанию были указаны так, как они есть в function_to_call, тогда большой блок if-elseif может быть заменен одним вызовом функции. Кроме того, расширенные параметры программы позволяют пользователю указать тип аргумента. Это позволит избежать разбора целых чисел, используя std :: stringstream. Наконец, хотя это может быть не особенно желательно, более надежная обработка аргументов по умолчанию с помощью опций программы Boost позволила бы получить полный набор вариантов для передачи или не передачи необязательных аргументов функции function_to_call. Как и сейчас, аргументы функции function_to_call должны быть указаны полностью слева направо, несмотря на то, что последние четыре аргумента являются необязательными.

+0

. Скопируйте и вставьте? –

+0

Нет, это просто то, что я знаю из опыта использования. –

3

Массив параметров командной строки является нулем, чтобы вы могли разобрать его один элемент, в то время как это:

void function_to_call(std::string arg1, 
        std::string arg2, 
        std::string arg3, 
        int num1, 
        int num2 
       ) 

{ 
    // do fancy stuff here 
    std::cout << "arg1: " << arg1 << '\n'; 
    std::cout << "arg2: " << arg2 << '\n'; 
    std::cout << "arg3: " << arg3 << '\n'; 
    std::cout << "num1: " << num1 << '\n'; 
    std::cout << "num2: " << num2 << '\n'; 
} 

struct config 
{ 
    std::string arg1; 
    std::string arg2; 
    std::string arg3 = "wibble"; // set arg3 default here 
    int arg4 = 1;    // set arg4 default here 
    int arg5 = 0;    // set arg5 default here 
}; 

config parse_command_params(char** argv) 
{ 
    config cfg; 

    if(!argv[1]) 
     throw std::runtime_error("At least 2 args required"); 

    cfg.arg1 = argv[1]; 

    if(!argv[2]) 
     throw std::runtime_error("At least 2 args required"); 

    cfg.arg2 = argv[2]; 

    // optional from here on 

    if(!argv[3]) 
     return cfg; 

    cfg.arg3 = argv[3]; 

    if(!argv[4]) 
     return cfg; 

    cfg.arg4 = std::stoi(argv[4]); 

    if(!argv[5]) 
     return cfg; 

    cfg.arg5 = std::stoi(argv[5]); 

    return cfg; 
} 

int main(int, char** argv) 
{ 
    try 
    { 
     config cfg = parse_command_params(argv); 

     function_to_call(cfg.arg1, cfg.arg2, cfg.arg3, cfg.arg4, cfg.arg5); 
    } 
    catch(std::exception const& e) 
    { 
     std::cerr << e.what() << '\n'; 
     return EXIT_FAILURE; 
    } 

    return EXIT_SUCCESS; 
} 

Сохраняя параметры в struct я могу использовать его, чтобы установить значения по умолчанию для необязательных параметров и просто игнорируйте их, если они отсутствуют в параметрах, предоставленных пользователем.

Примечание: Отредактировано для включения предложения @ cmaster в синтаксический анализ для выделенной функции.

+1

Я хотел бы добавить, что вы можете избежать '' if (argv [3]) '-arrow в нижней части, перемещая синтаксический анализ параметра в специальную функцию: это позволит вам просто вернуться раньше с помощью' if (! argv [3]) return; cfg.arg3 = argv [3]; ... ' – cmaster

+0

@cmaster Да, что делает необязательные параметры менее подверженными ошибкам, если вы забыли их вложить - * как в моем исходном сообщении. * :) – Galik

0

Если все, что вы ищете, это быстрый и грязный способ достижения легко масштабируемого списка параметров, вы можете попробовать что-то в следующих строках: Он имеет дополнительное преимущество, что вы можете определить значения по умолчанию для дополнительных параметров и включить эти значения по умолчанию в распечатку. Это также означает, что когда вы хотите добавить дополнительные обязательные или необязательные аргументы, вам просто нужно добавить их в список в методах DefineRequiredArgs или DefineOptionalArgs, и у вас есть доступ к ним в других местах.

// multi_option_helper.cpp 

#include <iostream> 
#include <sstream> 
#include <string> 
#include <vector> 

void function_to_call(std::vector<std::vector<std::string> > &req_args, 
         std::vector<std::vector<std::string> > &opt_args, 
         int num1 = 1, 
         int num2 = 2 
        ) 

{ 
    // do fancy stuff here 

    // Print required options 
    std::cout << "Required Options:\n" ; 
    for (int i=0; i<req_args.size(); i++) { 
     std::cout << "\t" << req_args[i][0] << " = " << req_args[i][1] << std::endl; 
    } 
    // Print optional options 
    std::cout << "Optional Options:\n" ; 
    for (int i=0; i<opt_args.size(); i++) { 
     std::cout << "\t" << opt_args[i][0] << " = " << opt_args[i][1] << std::endl; 
    } 

} 

std::vector<std::vector<std::string> > DefineRequiredArgs() 
{ 
    // Define the required arguments 
    std::vector<std::vector<std::string> > req_args ; 

    /* pre-c++11 way of doing it */ 
    // Define a generic vector of strings 
    std::vector<std::string> arg(2) ; 
    arg[1] = "" ; 
    arg[0] = "1st_argument" ; 
    req_args.push_back(arg) ; 
    arg[0] = "2nd_argument" ; 
    req_args.push_back(arg) ; 
    arg[0] = "3rd_argument" ; 
    req_args.push_back(arg) ; 
    arg[0] = "4th_argument" ; 
    req_args.push_back(arg) ; 
    // ... continue this process as many times as needed 

    /* post-c++11 way of doing it 
    req_args.push_back({"1st_argument", ""}) ; 
    req_args.push_back({"2nd_argument", ""}) ; 
    req_args.push_back({"3rd_argument", ""}) ; 
    req_args.push_back({"4th_argument", ""}) ; 
    */ 

    return req_args ; 
} 

std::vector<std::vector<std::string> > DefineOptionalArgs() 
{ 
    // Define the required arguments 
    std::vector<std::vector<std::string> > opt_args ; 

    // pre-c++11 
    std::vector<std::string> arg(2) ; 
    arg[1] = "" ; 
    arg[0] = "5th_argument" ; 
    arg[1] = "foo" ; 
    opt_args.push_back(arg) ; 
    arg[0] = "6th_argument" ; 
    arg[1] = "bar" ; 
    opt_args.push_back(arg) ; 
    arg[0] = "7th_argument" ; 
    arg[1] = "521600" ; 
    opt_args.push_back(arg) ; 
    arg[0] = "8th_argument" ; 
    arg[1] = "86" ; 
    opt_args.push_back(arg) ; 
    arg[0] = "9th_argument" ; 
    arg[1] = "somethingelse" ; 
    opt_args.push_back(arg) ; 
    // ... continue this process as many times as needed 

    /* c++11 alternative 
    opt_args.push_back({"5th_argument", "foo"}) ; 
    opt_args.push_back({"6th_argument", "bar"}) ; 
    opt_args.push_back({"7th_argument", "521600"}) ; 
    opt_args.push_back({"8th_argument", "86"}) ; 
    opt_args.push_back({"9th_argument", "somethingelse"}) ; 
    */ 

    return opt_args ; 
} 

int main(int argc, char** argv) 
{ 
    // Get the required options 
    std::vector<std::vector<std::string> > req_args = DefineRequiredArgs() ; 
    // Get the optionsl options 
    std::vector<std::vector<std::string> > opt_args = DefineOptionalArgs() ; 

    if(argc < req_args.size()+1) { 
     std::cerr << "Usage: \n\t" << argv[0] ; 
     // Print the required arguments 
     for (int i=0; i<req_args.size(); i++) { 
      std::cerr << "\n\t" << req_args[i][0] ; 
     } 
     // Print the optional arguments 
     for (int i=0; i<req_args.size(); i++) { 
      std::cerr << "\n\t" << opt_args[i][0] 
      << " (optional Default=" << opt_args[i][1] << ")" ; 
     } 
     std::cerr << std::endl; 
    } else { 
     // Fill the required options 
     int opt_counter(1) ; 
     while ((opt_counter <= req_args.size())) { 
      req_args[opt_counter-1][1] = std::string(argv[opt_counter]) ; 
      opt_counter++ ; 
     } 
     // Now fill the optional options 
     int offset(req_args.size()+1) ; // Note the additional offset of '1' 
     while ((opt_counter < argc)) { 
      opt_args[opt_counter-offset][1] = std::string(argv[opt_counter]) ; 
      opt_counter++ ; 
     } 
     // Fill num1 and num2 
     int num1, num2 ; 
     std::stringstream stream ; 
     stream << opt_args[2][1] << ' ' << opt_args[3][1] ; 
     stream >> num1 >> num2 ; 

     /* c++11 alternative 
     int num1 = std::stoi(opt_args[2][1]) ; 
     int num2 = std::stoi(opt_args[3][1]) ; 
     */ 
     // Now call the helper function 
     function_to_call(req_args, opt_args, num1, num2) ; 
    } 

    return 0; 
} 

Теперь, когда вы работаете с меньшим, чем требуемое количество вариантов вы получите распечатку:

Usage: 
    ./multi_option_helper 
    1st_argument 
    2nd_argument 
    3rd_argument 
    4th_argument 
    5th_argument (optional Default=foo) 
    7th_argument (optional Default=521600) 
    8th_argument (optional Default=86) 
    9th_argument (optional Default=somethingelse) 

Обратите внимание, что поскольку тег только «C++», а не «C++ 11 «Я включил только код, который будет компилировать, делая g++ multi_option_helper.cpp -o multi_option_helper, однако есть несколько альтернатив C++ 11, которые упрощают работу.

С другой стороны, если вы ищете более то, что вы ищете, то, что позволяет вам создавать именованные параметры (например, --Arg1=arg1_val), вы можете взглянуть на «GetOpt». Одним из преимуществ этого является способность вашего пользователя передавать параметры в любом порядке. Вы также можете создать вариант справки (обычно это опция -h или --help в некоторых программах).

Личное примечание: Я стараюсь избегать использования методов BOOST, потому что они добавляют дополнительную зависимость к моему коду. Либо я должен включать заголовочные файлы в свой пакет (я не уверен, какое лицензирование BOOST использует, так что это может быть даже не законным), или требуется, чтобы пользователь заходил и загружал их, а затем загружал библиотеку самостоятельно. Вот почему я предпочитаю GetOpt, так как большинство людей, использующих современные компиляторы, уже должны иметь к нему доступ. Я также разработал собственный класс обработчика параметров командной строки, чтобы упростить определение и использование параметров командной строки. Проверьте это here, если вам интересно.

Смежные вопросы