Boost.ProgramOptions用法和原理介绍
Boost.ProgramOptions是Boost中一个专门用来解析命令行的库,其目标是轻松的解析命令行选项。
要使用Boost.ProgramOptions解析命令行选项,需要执行以下三个步骤:
-
定义命令行选项。可以为它们指定名称,并指定可以将哪些名称设置为值。如果将命令行选项解析为key/value类型,则还可以设置值的类型,例如,string或者int以及其他基本类型。
-
使用解析器评估命令行。可以从main()函数中的两个参数获得命令行,通常称为argc和argv。
-
存储解析器评估的命令行选项。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,首先是引入boost的program_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_error。Boost.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允许您对环境变量进行迭代。