Boost.ProgramOptions用法和原理介绍

Boost.ProgramOptionsBoost中一个专门用来解析命令行的库,其目标是轻松的解析命令行选项。

要使用Boost.ProgramOptions解析命令行选项,需要执行以下三个步骤:

  1. 定义命令行选项。可以为它们指定名称,并指定可以将哪些名称设置为值如果将命令行选项解析为key/value类型,则还可以设置值的类型,例如,string或者int以及其他基本类型。

  2. 使用解析器评估命令行。可以从main()函数中的两个参数获得命令行,通常称为argcargv

  3. 存储解析器评估的命令行选项。Boost.ProgramOptions提供了一个派生自std::map其的类,该类将命令行选项另存为名称/值对。然后,您可以检查已存储的选项及其值。

boost::program_options::options_description解释

boost::program_options::option_description是描述命令行选项的类,可以将这种类型的对象写入std::cout之类的流中,以显示可用命令行选项的概述。传递给构造函数的字符串为概览提供了一个名称,作为命令行选项的标题。

program_options::options_description定义了一个成员函数add(),它需要一个boost::program_options::option_description类型的参数。通过调用这个函数来描述任何一个命令行选项。如果需要描述多个命令行选项,可以使用add_options()

add_options()返回一个代理对象,它表示一个boost::program_options::options_description类型的对象。代理对象的类型并不重要,但是,代理对象简化了许多命令行选项的定义。它使用重载的operator()方法,您可以调用它来传递所需的数据来定义命令行选项。operator()返回对同一代理对象的引用,并且允许您可以调用多次operator()方法。

要想使用Boost.ProgramOptions首先是引入boostprogram_options.hpp的头文件,通过该库的boost::program_options可以使用所有的类和函数。

给个官方例子:

例1:Boost.ProgramOptions的基本方法

#include <boost/program_options.hpp>
#include <iostream>

using namespace boost::program_options;
using namespace std;

void on_age(int age) {
    cout << "On Age" << age << "\n";
}

int main(int argc, char *[] argv) {
    try {
        options_description desc{"Options"};
        desc.add_options()
                ("help,h", "Help screen")
                ("pi", value<float>()->default_value(3.14f), "Pi")
                ("age", value<int>()->notifier(on_age), "Age");
        variables_map vm;
        store(parse_command_line(argc, argv, desc), vm);
        notify(vm);
        if (vm.count("help")) {
            cout << desc << endl;
        } else if (vm.count("age")) {
            cout << "Age is :" << vm["age"].as<int>() << endl;
        } else if (vm.count("pi")) {
            cout << "Pi:" << vm["pi"].as<float>() << endl;
        }
    } catch (const error &ex) {
        std::cerr << ex.what() << "\n";
    }

}

例1展示了使用Boost.ProgramOptions解析命令行选项的基本方法。

例1解析

上述例子在代理对象的帮助下定义了3个命令行选项,第一个是--help,此选项的描述设置为“Help Screen”,该选项设置为一个开关,而不是key/value类型。您可以在CLI上设置--help命令或者忽略这个命令,但是不可能为 --help命令设置值。

另外,传递给operator()的第一个字符串是"help,h",该设置的结果是可以在CLI上使用--help选项或者--h选项,这两个选项的实际效果都是一样的。

另外两个命令是 --pi--age,这两个命令都是key/value类型的, --pi--age都期望设置一个值。

将指向类型为boost::program_options::value_semantic的对象的指针作为第二个参数传递给operator(),以将选项定义为名称/值对。您不需要直接访问boost::program_options::value_semantic您可以使用helper函数boost::program_options::value(),它创建了一个boost::program_options::value_semantic类型的对象。program_options::value()返回对象的地址,然后可以使用operator()将其传递给代理对象。

program_options::value()是一个函数模板,它接受命令行选项值的类型作为模板参数。因此,命令行选项--age要求整数,--pi要求浮点数。

boost::program_options::value()返回的对象提供了一些有用的成员函数。例如,您可以调用default_value()来提供一个默认值。例子中如果没有在命令行中给--pi设置值,则默认为3.14。notifier()将函数链接到命令行选项的值。然后使用命令行选项的值调用该函数。在例中,函数on_age()链接到--age如果使用命令行选项--age来设置年龄,则将年龄传递给on_age(),后者将其写入标准输出。使用on_age()之类的函数处理值是可选的。您不必使用notifier(),因为可以通过其他方式访问值。

 在定义了所有命令行选项之后,就可以使用解析器了。在中,调用helper函数boost::program_options::parse_command_line()来解析命令行。这个函数接受argc和argv(定义命令行),以及desc(包含选项描述)。program_options::parse_command_line()返回类型为boost::program_options::parsed_options的对象中已解析的选项。通常不直接访问此对象。而是将其传递给boost::program_options::store(),它将解析后的选项存储在容器中。

例子中将vm作为第二个参数传递给boost::program_options::store()vm是boost::program_options::variables_map类型的对象。这个类派生自std::map<std::string, boost::program_options::variable_value>类,因此提供了与std::map相同的成员函数。例如,可以调用count()来检查是否使用了某个命令行选项并将其存储在容器中。

在例中,在访问vm并调用count()之前,调用boost::program_options::notify()此函数触发on_age()等函数,这些函数使用notifier()链接到一个值。如果没有boost::program_options::notify(),就不会调用on_age()

vm允许您检查某个命令行选项是否存在,还允许您访问命令行选项设置为的值。值的类型是boost::program_options::variable_value,一个在内部使用boost::any的类。可以从成员函数value()获取boost::any类型的对象。

例中调用as(),而不是value()此成员函数将命令行选项的值转换为作为模板参数传递的类型。as()使用boost::any_cast()进行类型转换。

可以通过多种方式启动例1,一下是一个例子(假设例1的文件名是test)

test

在本例中显示Pi: 3.14。因为在命令行上没有设置pi,所以会显示默认值。

下面的例子使用了pi:

test --pi 3.1415

现在显示 Pi:3.1415,下面的例子显示pi和age:

test --pi 3.1415 --age 29

现在输出是On age: 29 Age is:29调用boost::program_options::notify()时写入第一行;这将触发on_age()的执行--pi没有输出,因为程序使用else if语句,这些语句只显示设置--pi i或--age的值。

显示help:

test -h

你可以得到所有命令行选项的完整概述:

Options: -h [ --help ]         Help screen --pi arg (=3.1400001) Pi --age arg             Age is:

可以看到,帮助可以以两种不同的方式显示,因为该命令行选项的短名称已经定义。将显示默认值。命令行选项及其描述将自动格式化。只需将类型为boost::program_options::options_description的对象写入标准输出.

这样测试:

test --age

显示如下:

the required argument for option '--age' is missing.

因为--age没有设置,boost::program_options::parse_command_line()中使用的解析器会抛出一个boost::program_options::error类型的异常。catch捕获了这个异常,并将错误消息写入标准输出。

boost::program_options::error派生自std::logic_errorBoost.ProgramOptions定义了额外的异常,这些异常都来自boost::program_options::error其中一个异常是boost::program_options::invalid_syntax,如果不提供--age的值,则在示例1中抛出的正是这个异常。

例2:Boost.ProgramOptions的特殊配置设置

 1 #include <boost/program_options.hpp>
 2 #include <string>
 3 #include <vector>
 4 #include <algorithm>
 5 #include <iterator>
 6 #include <iostream>
 7 
 8 using namespace boost::program_options;
 9 
10 void to_cout(const std::vector<std::string> &v)
11 {
12   std::copy(v.begin(), v.end(), std::ostream_iterator<std::string>{
13     std::cout, "\n"});
14 }
15 
16 int main(int argc, const char *argv[])
17 {
18   try
19   {
20     int age;
21 
22     options_description desc{"Options"};
23     desc.add_options()
24       ("help,h", "Help screen")
25       ("pi", value<float>()->implicit_value(3.14f), "Pi")
26       ("age", value<int>(&age), "Age")
27       ("phone", value<std::vector<std::string>>()->multitoken()->
28         zero_tokens()->composing(), "Phone")
29       ("unreg", "Unrecognized options");
30 
31     command_line_parser parser{argc, argv};
32     parser.options(desc).allow_unregistered().style(
33       command_line_style::default_style |
34       command_line_style::allow_slash_for_short);
35     parsed_options parsed_options = parser.run();
36 
37     variables_map vm;
38     store(parsed_options, vm);
39     notify(vm);
40 
41     if (vm.count("help"))
42       std::cout << desc << '\n';
43     else if (vm.count("age"))
44       std::cout << "Age: " << age << '\n';
45     else if (vm.count("phone"))
46       to_cout(vm["phone"].as<std::vector<std::string>>());
47     else if (vm.count("unreg"))
48       to_cout(collect_unrecognized(parsed_options.options,
49         exclude_positional));
50     else if (vm.count("pi"))
51       std::cout << "Pi: " << vm["pi"].as<float>() << '\n';
52   }
53   catch (const error &ex)
54   {
55     std::cerr << ex.what() << '\n';
56   }
57 }

例2介绍了Boost.ProgramOptions提供的更多配置设置

例2解析

示例2与示例1一样解析命令行选项。然而,有一些显著的差异。例如,在定义--pi命令行选项时调用implicit_value(),而不是default_value()这意味着pi的默认值不是3.14。--pi必须在命令行上设置pi才能使用。但是,如果使用implicit_value(),则不需要向--pi命令行选项提供值在这种情况下,pi隐式地设置为3.14。

对于命令行选项--age,将指向变量age的指针传递给boost::program_options::value()它将命令行选项的值存储在变量中。当然,该值在容器vm中仍然可用。

请注意,只有在调用boost::program_options::notify()时,值才存储在age中。虽然本例中没有使用notifier(),但是必须使用boost::program_options::notify()为了避免麻烦,最好总是在解析的命令行选项与boost::program_options::store()存储之后调用boost::program_options::notify()

例2支持一个新的命令行选项--phone,将电话号码传递给程序。实际上,您可以在命令行上传递多个电话号码。例如,下面的命令行使用电话号码123和456启动程序:(假设例2的名称是test)

test --phone 123 456

例2支持多个电话号码,因为multitoken()是在这个命令行选项的值上调用的。而且,由于调用了zero_token(),所以也可以使用phone而不传递电话号码。

您还可以通过重复--phone选项来传递多个电话号码,如下面的命令行所示:

test --phone 123 --phone 456

在本例中,分析了phone的数123和456。调用composing()使多次使用命令行选项成为可能——这些值是组合的。

参数 --phone的值类型是std::vector<std::string>您需要使用一个vector来存储多个phone数字。

示例2定义了另一个命令行选项--unreg这是一个不能设置为值的开关。稍后在示例中使用它来决定是否显示在desc中没有定义的命令行选项。

虽然示例1调用函数boost::program_options::parse_command_line()来解析命令行选项,但是示例2使用了boost::program_options::command_line_parser类型的解析器。argc和argv被传递给构造函数。command_line_parser提供了几个成员函数。必须调用options()将命令行选项的定义传递给解析器。

与其他成员函数一样,options()返回对同一解析器的引用。这样,可以方便地逐个调用成员函数。例2在options()之后调用allow_unregistered(),告诉解析器如果检测到未知的命令行选项,不要抛出异常。最后,调用style()来告诉解析器短名称可以与斜杠一起使用。因此-help选项的短名称可以是-h或/h。

请注意boost::program_options::parse_command_line()支持第四个参数,该参数被转发给style()如果您想使用boost::program_options::command_line_style::allow_slash_for_short这样的选项,您仍然可以使用boost::program_options::parse_command_line()函数。

设置配置之后,在解析器上调用run()这个成员函数在一个boost::program_options::parsed_options类型的对象中返回解析过的命令行选项,您可以将其传递给boost::program_options::store()以将选项存储在vm中。

在后面的代码中,示例2再次访问vm来评估命令行选项。只有对boost::program_options:: collect_un()是新的调用,这个函数被命令行选项unreg调用。该函数需要一个boost::program_options::parsed_options类型的对象,该对象由run()返回。它返回std::vector中的所有未知命令行选项。例如,如果您用test --unreg --abc--abc来启动程序,那么abc将被写入标准输出。

boost::program_options::exclude_position作为第二个参数传递到boost::program_options:: collect_un()时,positional选项将被忽略。

在例2中,因为没有定义positional选项,所以boost::program_options::exclude_positional选项并不重要,但是boost::program_options::collect_unrecognized()需要这个选项。

例3:带Boost.ProgramOptions的位置选项

 1 #include <boost/program_options.hpp>
 2 #include <string>
 3 #include <vector>
 4 #include <algorithm>
 5 #include <iterator>
 6 #include <iostream>
 7 
 8 using namespace boost::program_options;
 9 
10 void to_cout(const std::vector<std::string> &v)
11 {
12   std::copy(v.begin(), v.end(),
13     std::ostream_iterator<std::string>{std::cout, "\n"});
14 }
15 
16 int main(int argc, const char *argv[])
17 {
18   try
19   {
20     options_description desc{"Options"};
21     desc.add_options()
22       ("help,h", "Help screen")
23       ("phone", value<std::vector<std::string>>()->
24         multitoken()->zero_tokens()->composing(), "Phone");
25 
26     positional_options_description pos_desc;
27     pos_desc.add("phone", -1);
28 
29     command_line_parser parser{argc, argv};
30     parser.options(desc).positional(pos_desc).allow_unregistered();
31     parsed_options parsed_options = parser.run();
32 
33     variables_map vm;
34     store(parsed_options, vm);
35     notify(vm);
36 
37     if (vm.count("help"))
38       std::cout << desc << '\n';
39     else if (vm.count("phone"))
40       to_cout(vm["phone"].as<std::vector<std::string>>());
41   }
42   catch (const error &ex)
43   {
44     std::cerr << ex.what() << '\n';
45   }
46 }

例3说明了positional选项的详细用法

例3解析

示例3使用boost::program_options::positional_options_description类将--phone定义为一个positional选项。这个类提供了成员函数add(),它期望传递命令行选项的名称和位置。示例传递了“phone”和-1。

使用positional选项,可以在命令行上设置值,而不需要使用命令行选项。你可以这样调用:

test 123 456

即使没有使用--phone,123和456也被识别为电话号码。

boost::program_options::positional_options_description类型的对象上调用add(),将命令行上的值分配给使用位置号的命令行选项。使用命令行test 123 456调用示例3时,123的位置号是0,456的位置号是1。示例3将-1传递给add(),它将所有的值(123和456)赋给--phone如果将示例3更改为将值0传递给add(),则只有123可以被识别为电话号码。如果将1传递给add(),那么只能识别456。

pos_desc与position()一起传递给解析器。这就是解析器如何知道哪些命令行选项的位置的。

请注意,您必须确保已定义了位置选项。例如,在例3中,“phone”只能传递给add(),因为在desc中已经存--phone的定义。

在前面所有的例子中,Boost.ProgramOptions用于解析命令行选项。但是,这个库也支持从文件中加载配置选项。如果必须重复设置相同的命令行选项,这可能很有用。

例4:从配置文件加载选项

 1 #include <boost/program_options.hpp>
 2 #include <string>
 3 #include <fstream>
 4 #include <iostream>
 5 
 6 using namespace boost::program_options;
 7 
 8 int main(int argc, const char *argv[])
 9 {
10   try
11   {
12     options_description generalOptions{"General"};
13     generalOptions.add_options()
14       ("help,h", "Help screen")
15       ("config", value<std::string>(), "Config file");
16 
17     options_description fileOptions{"File"};
18     fileOptions.add_options()
19       ("age", value<int>(), "Age");
20 
21     variables_map vm;
22     store(parse_command_line(argc, argv, generalOptions), vm);
23     if (vm.count("config"))
24     {
25       std::ifstream ifs{vm["config"].as<std::string>().c_str()};
26       if (ifs)
27         store(parse_config_file(ifs, fileOptions), vm);
28     }
29     notify(vm);
30 
31     if (vm.count("help"))
32       std::cout << generalOptions << '\n';
33     else if (vm.count("age"))
34       std::cout << "Your age is: " << vm["age"].as<int>() << '\n';
35   }
36   catch (const error &ex)
37   {
38     std::cerr << ex.what() << '\n';
39   }
40 }

例4解析

例4使用了boost::program_options::options_description类型的两个对象。general选项定义必须在命令行上设置的选项。fileOptions定义可以从配置文件加载的选项。

并不是强制使用boost::program_options::options_description类型的两个不同对象来定义选项。如果命令行和文件的选项集相同,则只能使用一个。在例4中,分隔选项是有意义的,因为您不希望在配置文件中设置help如果允许这样做,并且用户将该选项放入配置文件中,程序将每次显示Help screen

例4从配置文件加载--age您可以将配置文件的名称作为命令行选项传递。在本例中,--config就是为此在generalOptions中定义的。

使用boost::program_options::parse_command_line()解析命令行选项并将其存储在vm中之后,示例将检查是否设置了--config。如果设置了,则使用std::ifstream打开配置文件。std::ifstream对象与描述选项的fileOptions一起传递给函数boost::program_options::parse_config_file()program_options:: parse_file()执行与boost::program_options::parse_command_line()相同的操作,并在boost::program_options::parsed_options类型的对象中返回已解析的选项。这个对象被传递给boost::program_options::store()来存储vm中解析过的选项。

如果创建一个名为config的文件。在该文件中输入age=29,然后执行下面的命令行,您将得到显示的结果:

test --config config.txt

结果显示为:

Your age is: 29

如果您在命令行和配置文件中支持相同的选项,那么您的程序可能会解析相同的选项两次:一次使用boost::program_options::parse_command_line(),另一次使用boost::program_options::parse_config_file()函数调用的顺序决定了您将在vm中找到的值。命令行选项的值一旦存储在vm中,就不会被覆盖。该值是由命令行上的选项设置的还是在配置文件中设置的,这仅取决于调用store()函数的顺序。

Boost.ProgramOptions还定义了函数boost::program_options::parse_environment(),它可用于从环境变量加载选项。boost::environment_iterator允许您对环境变量进行迭代。

posted @ 2019-11-20 16:59  重设代码的天空  阅读(3287)  评论(0编辑  收藏  举报