1、阅读rviz中的源码时在rviz/visualizer_app.cpp中遇到如下代码:
po::options_description options; options.add_options() ("help,h", "Produce this help message") ("splash-screen,s", po::value<std::string>(), "A custom splash-screen image to display") ("help-file", po::value<std::string>(), "A custom html file to show as the help screen") ("display-config,d", po::value<std::string>(), "A display config file (.rviz) to load") ("fixed-frame,f", po::value<std::string>(), "Set the fixed frame") ("ogre-log,l", "Enable the Ogre.log file (output in cwd) and console output.") ("in-mc-wrapper", "Signal that this is running inside a master-chooser wrapper") ("opengl", po::value<int>(), "Force OpenGL version (use '--opengl 210' for OpenGL 2.1 compatibility mode)") ("disable-anti-aliasing", "Prevent rviz from trying to use anti-aliasing when rendering.") ("no-stereo", "Disable the use of stereo rendering.") ("verbose,v", "Enable debug visualizations") ("log-level-debug", "Sets the ROS logger level to debug.");
其中po是命名空间:namespace po = boost::program_options;
看到options对象的成员函数add_options()后面接了一连串的括号,表示很不理解,在网上查了下相关资料,整理下自己的学习所得,顺便在这里记录下来。
实际上上面使用了boost库中的program_options,即程序参数项,它是由一系列的(name,value)键值对构成,program_options允许程序开发者通过命令行和配置文件获取这些参数项。
program_options的使用主要通过下面三个组件完成:
组件名 作用
options_description(选项描述器) 描述当前的程序定义了哪些选项
parse_command_line(选项分析器) 解析由命令行输入的参数
variables_map(选项存储器) 容器,用于存储解析后的选项
使用时添加头文件
#include <boost/program_options.hpp>
可以把命名空间简化:
namespace po = boost::program_options;
program_options代码流程
- 构造option_description对象和variables_map对象
- add_options()->向option_description对象添加选项
- parse_command_line()->将命令行输入的参数解析出来
- store()->将解析出的选项存储至variables_map中
- notify()->通知variables_map去更新所有的外部变量
- count()->检测某个选项是否被输入
- operator[]->取出选项的值
program_options对象的成员函数add_options()后面可以加无限多的()是因为此函数返回值类型是options_description_easy_init,函数申明如下:
options_description_easy_init add_options();
options_description_easy_init的类型为:
/** Class which provides convenient creation syntax to option_description. */ class BOOST_PROGRAM_OPTIONS_DECL options_description_easy_init { public: options_description_easy_init(options_description* owner); options_description_easy_init& operator()(const char* name, const char* description); options_description_easy_init& operator()(const char* name, const value_semantic* s); options_description_easy_init& operator()(const char* name, const value_semantic* s, const char* description); private: options_description* owner; };
显然,类重载了()符号。
使用program_options的好处:
1、使用更容易。定义参数处理的语法简单,库自身很小,比如转换参数值到指定的类型和保存参数值到变量的事情都是自动处理。
2、错误报告更友好。可报告错误的命令行参数,库能自动生成使用帮助,避免手工更新使用帮助导致的不一致。
3、参数能从不同的地方读取,当命令行参数不能满足要求,需要改用配置文件或环境变量,这些功能都能支持,代码改动很小。
使用示例:
#include <iostream> #include <boost/program_options.hpp> using namespace std; using namespace boost; using namespace boost::program_options; int main(int argc,char* argv[]) { string config = ""; string data_type = ""; string version = ""; try { options_description desc("Allowed options"); desc.add_options() ("help,h", "produce help message") ("config,c", value<string>()->default_value(""), "set config") ("data_type,t", value<string>()->default_value(""), "set data type") ("version,v", value<string>()->default_value(""), "set version"); variables_map vm; store(parse_command_line(argc, argv, desc), vm); notify(vm); if (vm.count("help")) { cerr << desc << " "; return 1; } if (vm.count("config")) { if (vm["config"].as<string>().empty()) { cerr<<"config is empty!!!"<<endl; return -1; } else { config = vm["config"].as<string>(); cerr<<"config is: "<<config<<endl; } } if (vm.count("data_type")) { if (vm["data_type"].as<string>().empty()) { cerr<<"data_type is empty!!!"<<endl; return -1; } else { data_type = vm["data_type"].as<string>(); //cerr<<"data_type is: "<<data_type<<endl; } } if (vm.count("version")) { if (vm["version"].as<string>().empty()) { //cerr<<"version is empty!!!"<<endl; } else { version = vm["version"].as<string>(); //cerr<<"version is: "<<version<<endl; } } } catch (std::exception& e) { cerr<<"error: "+string(e.what())<<endl; return -1; } catch(...) { cerr<<"Exception of unknown type!"<<endl; return -1; } search_business::BaseBusiness bp(data_type); if (false == bp.init(config, version)) { cerr << "init error" << endl; return 1; } if (bp.work() != 0) { cerr << "work error" << endl; return 2; } return 0; }
以下内容来源于以下两个博客
https://blog.csdn.net/cchd0001/article/details/43967451 https://blog.csdn.net/morning_color/article/details/50241987
定义并添加命令说明
int opt; /* 类 options_description 是存储选项详细信息的类 */ options_description parser("name_your_like"); /*add_option() 支持() 操作. 多次调用add_options() 的效果等同于将所有的选项使用() 并列声明*/ parser.add_options() /*无参数的选项格式 : "命令名" , "命令说明" */ ("help", "produce help message") /* 有参赛的选项格式 : "命令名" , "参数说明" , "命令说明" */ ("set-float" ,value<float>(), "set a float parameter") /* 参数对应已有数据(特定地址) , 并且有初始值 : */ ("optimization", value<int>(&opt)->default_value(10), "optimization level") /* 支持短选项 ( --include-path 和 -I 等效 ) , 多次调用存在依次vector中 */ ("include-path,I", value< vector<string> >(), "include path") ;
解析命令行 , 存储在variables_map 类的变量中
/* variables_map 类是用来存储具体传入参数数据的类 , 和map很相似. 使用as接口来传出数据 */ variables_map vm; /* ac 就是参数个数 , av 就是参数列表指针 */ /* int ac , const char ** av */ /* parse_command_line 接口不支持猜测选项 */ store(parse_command_line(ac, av, desc), vm); notify(vm);
注意: 如果传入没有定义的选项, 会抛出异常!
判断命令是否触发 , 以 help 为例
/* variables_map 使用 count 接口来判断具体选项是否触发 */ if (vm.count("help")) { /*如果触发就输出命令说明 , option_description 类重载了 << 行为, 将格式化的输出 命令说明 */ cout << desc << " "; return 1; }
取出传入的参数 , 以上面的float参数为例
首先, 如果已经指定了一个接受数据 [ 比如 : ("optimization", value<int>(&opt)->default_value(10), "optimization level")
]的话,就可以直接使用它.
if (vm.count("set-float")) { cout << "set-float "<< /*map直接使用键值当index , 然后使用as< 具体类型> 接口来取出参数*/ vm["set-float"].as<float>() << ". "; } else { cout << "float not set "; }
定义默认参数.
上面的例子中 ,如下操作:
$ a.out –set-float=0.131415926 $ set-float 0.131415926
然而假设我们想要以下面的命令实现上面的效果:
$ a.out 0.131415926 $ set-float 0.131415926
就需要设置set-float 为默认的参数.
int opt; options_description parser("name_your_like"); parser.add_options() ("help", "produce help message") ("set-float" ,value<float>(), "set a float parameter") ("optimization", value<int>(&opt)->default_value(10), "optimization level") ("include-path,I", value< vector<string> >(), "include path") ; /* 定义一个可以猜测的命令 positional_options_description */ positional_options_description p; /* 指定猜测的时候参数的位置, -1 标识没有任何限制 */ p.add("set-float", -1); variables_map vm; /* 使用可以猜测的命令行解析接口来解析 */ store(command_line_parser(ac, av).options(desc).positional(p).run(), vm); notify(vm);
这样就支持了默认选项是 –set-float
对配置文件的支持
/*将文件读入流*/ ifstream ifs("file_name"); /* 调用解析文件接口 */ store(parse_config_file(ifs, desc), vm); notify(vm);
下面的代码是boost::program_options的一个简单的用法示例.
该示例中指定了两个选项,分别是–help和–filename.
//linux系统下,编译选项需加上 -lboost_program_options #include <iostream> #include <string> #include <boost/program_options.hpp> namespace bpo = boost::program_options; int main(int argc, char const *argv[]) { //步骤一: 构造选项描述器和选项存储器 //选项描述器,其参数为该描述器的名字 bpo::options_description opts("all options"); //选项存储器,继承自map容器 bpo::variables_map vm; //步骤二: 为选项描述器增加选项 //其参数依次为: key, value的类型,该选项的描述 opts.add_options() ("filename", bpo::value<std::string>(), "the file name which want to be found") ("help", "this is a program to find a specified file"); //步骤三: 先对命令行输入的参数做解析,而后将其存入选项存储器 //如果输入了未定义的选项,程序会抛出异常,所以对解析代码要用try-catch块包围 try{ //parse_command_line()对输入的选项做解析 //store()将解析后的结果存入选项存储器 bpo::store(bpo::parse_command_line(argc, argv, opts), vm); } catch(...){ std::cout << "输入的参数中存在未定义的选项! "; return 0; } //步骤四: 参数解析完毕,处理实际信息 //count()检测该选项是否被输入 if(vm.count("help") ){//若参数中有help选项 //options_description对象支持流输出, 会自动打印所有的选项信息 std::cout << opts << std::endl; } if(vm.count("filename") ){ //variables_map(选项存储器)是std::map的派生类,可以像关联容器一样使用, //通过operator[]来取出其中的元素.但其内部的元素类型value_type是boost::any, //用来存储不确定类型的参数值,必须通过模板成员函数as<type>()做类型转换后, //才能获取其具体值. std::cout << "find " << vm["filename"].as<std::string>() << std::endl; } if(vm.empty() ){ std::cout << "no options found "; } return 0; }
在编译后(假设在linux系统下其编译后的可执行文件为a.out)
输入
./a.out --help
则其输出为:
all options:
–filename arg the file name which want to be found
–help this is a program to find a specified file
输入
./a.out --filename=program_test
则其输出为
find program_test
若不指定选项,即输入
./a.out
则输出为
no options found
示例二
下面的这个示例主要用来说明外部变量,参数默认值以及一个选项对应多个值的情况
这段代码主要有四个选项:
/* –apple : 苹果的数量 –orange:橘子的数量 –address:水果的生产地,可指定多个生产地 –help: 打印帮助信息 */ /////////////////////////////////////////// //计算橘子和苹果的总数量,可以指定多个生产地 // //编译选项加上 -lboost_program_options // /////////////////////////////////////////// #include <iostream> #include <vector> #include <string> #include <boost/program_options.hpp> namespace bpo = boost::program_options; int main(int argc, char const *argv[]) { //外部变量,用于保存获取的参数值 int apple_num = 0, orange_num = 0; std::vector<std::string> addr; bpo::options_description opt("all options"); opt.add_options() //指定该参数的默认值 // "apple,a" : 指定选项的全写形式为 --apple, 简写形式为 -a //value<type>(ptr) : ptr为该选项对应的外部变量的地址, 当该选项被解析后, //可通过下面的notify()函数将选项的值赋给该外部变量,该变量的值会自动更新 //defaut_value(num) : num为该选项的默认值, 若命令行中未输入该选项, 则该选项的值取为num ("apple,a", bpo::value<int>(&apple_num)->default_value(10), "苹果的数量") ("orange,o", bpo::value<int>(&orange_num)->default_value(20), "橘子的数量") //该参数的实际类型为vector,所以命令行中输入的值可以是多个, //multitoken()的作用就是告诉编译器,该选项可接受多个值 ("address", bpo::value<std::vector<std::string> >()->multitoken(), "生产地") ("help", "计算苹果和橘子的总数量"); bpo::variables_map vm; try{ bpo::store(parse_command_line(argc, argv, opt), vm); } catch(...){ std::cout << "输入的参数中存在未定义的选项! "; return 0; } //参数解析完成后,通知variables_map去更新所有的外部变量 //这句话执行后, 会使得apple_num和orange_num的值自动更新为选项指定的值 bpo::notify(vm); if(vm.count("help") ){ std::cout << opt << std::endl; return 0; } if(vm.count("address") ){ std::cout << "生产地为:"; //遍历选项值 for(auto& str : vm["address"].as<std::vector<std::string> >() ) std::cout << str << " "; std::cout << std::endl; } std::cout << "苹果的数量:" << apple_num << std::endl; std::cout << "橘子的数量:" << orange_num << std::endl; std::cout << "总数量数量:" << orange_num + apple_num << std::endl; return 0; }
输入
./a.out --help
输出
all options:
-a [ –apple ] arg (=10) 苹果的数量
-o [ –orange ] arg (=20) 橘子的数量
–address arg 生产地
–help 计算苹果和橘子的总数量
指定苹果和橘子的数量:
./a.out --apple=8 –orange=20
其输出为:
苹果的数量:8
橘子的数量:20
总数量数量:28
仅指定橘子的数量,不指定苹果的数量:
./a.out --orange=20
其输出为:
苹果的数量:10
橘子的数量:20
总数量数量:30
可以看到,由于没有输入苹果的数量,所以输出的苹果的数量为我们指定的默认值
指定一个生产地:
./a.out --apple=8 --orange=20 --address=山东
输出:
生产地为:山东
苹果的数量:8
橘子的数量:20
总数量数量:28
指定多个生产地:
./a.out --apple=8 --orange=20 --address=山东 陕西
输出为
生产地为:山东 陕西
苹果的数量:8
橘子的数量:20
总数量数量:28
简写形式的输入:
./a.out -a 8 -o 20 --address=山东
输出:
生产地为:山东
苹果的数量:8
橘子的数量:20
总数量数量:28