处理 main 中的可选参数

Handling optional parameters in main

假设我有一个 main 函数,它基本上只是调用另一个函数作为程序的入口点。该函数(以及整个程序)有一些强制参数和一些可选参数:

#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 库或 boost program_options 稍微整理一下,但这并没有真正改变概念上的事情。

我是否缺少一种明显的方法来处理不同数量的参数?

Boost program options 是一个对解析输入参数非常有帮助的库。特别是,如果未在命令行中指定参数,则可以将参数指定为采用默认值。如果在 function_to_call 中指定默认参数,那么大的 if-elseif 块可以用一个函数调用替换。此外,boost 程序选项允许用户指定参数的类型。这将避免使用 std::stringstream 解析整数。最后,虽然这可能不是特别需要的,但 Boost 程序选项对默认参数的更健壮的处理将允许将可选参数传递或不传递给 function_to_call 的全套选择。按照现在的情况,function_to_call 的参数必须从左到右完全指定,尽管最后四个参数都是可选的。

命令行参数数组空终止所以你可以像这样一次解析一个元素:

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 将解析移至专用函数的建议。

我意识到这样做的一种方法可能是使用(平凡的)基 class 的向量,然后对不同类型进行小的专业化,例如像这样(不一定是最优的):

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 的数量非常少。

如果您正在寻找的只是一种快速而肮脏的方法来实现易于扩展的选项列表,您可以尝试以下方法。它还有一个额外的好处,即您可以为可选参数定义默认值并将这些默认值包含在打印输出中。这也意味着每当您想添加额外的必需或可选参数时,您只需将它们添加到 DefineRequiredArgsDefineOptionalArgs 方法的列表中,您就可以在其他地方访问它们。

// 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 的原因,因为大多数使用现代编译器的人应该已经可以访问它。我还开发了自己的命令行选项处理程序 class 来让我自己更轻松地定义和使用命令行选项。如果您感兴趣,请查看 here