在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: nt" << argv[0]
              << "ntt1st argument"
              << "ntt2nd argument"
              << "ntt3rd argument"
              << "ntt4th argument"
              << "ntt5th argument (optional)"
              << "ntt6th argument (optional)"
              << "ntt7th argument (optional)"
              << "ntt8th argument (optional)"
              << "ntt9th 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来整理,但这并没有真正改变概念。

是否有一个明显的方法,我错过了处理不同数量的参数?

命令行参数数组为以null结尾的,因此您可以一次解析一个元素,如下所示:

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的建议移动解析到一个专门的函数

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

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

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

如果您正在寻找的是一种快速而不太实用的方法来获得易于伸缩的选项列表,那么您可以尝试以下方法。它还有一个额外的好处,就是您可以为可选参数定义默认值,并将这些默认值包含在打印输出中。这也意味着当你想要添加额外的必需或可选参数时,你只需要将它们添加到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: nt" << argv[0] ;
        // Print the required arguments
        for (int i=0; i<req_args.size(); i++) {
            std::cerr << "nt" << req_args[i][0] ;
        }
        // Print the optional arguments
        for (int i=0; i<req_args.size(); i++) {
            std::cerr << "nt" << 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,因为大多数使用现代编译器的人应该已经可以访问它了。我还开发了自己的命令行选项处理程序类,使自己更容易定义和使用命令行选项。如果你感兴趣的话可以点击这里查看

相关内容

  • 没有找到相关文章

最新更新