1、gflags简介
1.1、gflags是什么
谷歌开源了三兄弟:gflags, glog, gtest 。
- gflags:这是Google开源的一套命令行参数解析工具,比getopt()函数功能更强大,使用起来更加方便,gflags还支持从环境变量和配置文件中读取参数。目前有C++和Python版本。
- glog:这是Google开源的工具,用来打印日志。
- gtest:这是Google开发的C++测试框架,它是一个库,通过在源代码中定义测试来帮助开发者进行单元测试。
1.2、gflags有什么作用
与getopt()/getopt_long函数的功能相似,都是解析命令行参数。比如我们执行 tar -czvf test.tar.gz ./test 命令将 test 目录压缩为 test.tar.gz 压缩包时,tar命令的源码中需要对 tar -czvf test.tar.gz ./test红色部分的参数进行解析,tar 工具才知道我们需要实现的具体功能。
gflags比getopt()函数功能更强大,使用起来更加方便,gflags除了和getopt函数一样支持从命令行读取参数,还支持从环境变量和配置文件中读取参数。但是不支持参数的简写(例如getopt支持–list缩写成-l,gflags不支持)。
1.3、为什么学gflags
SLAM开发过程中常会用到gflags, glog, gtest 这三件套,所以要简单学习下这三个开源模块的使用方法
1.4、如何学习
- 官方文档和示例代码:可以参考gflags的官方文档和示例代码,了解如何使用gflags库定义和解析命令行参数。
- 开源实现示例:可以参考一些开源的实现示例,例如使用gflags来解析命令行参数的程序。
2、gflags安装
2.1、在线安装gflags、glog、gtest
使用命令行安装最简单,但是版本可能比较老。使用源码编译安装,安装步骤复杂,但可靠稳定。使用命令行安装谷歌三件套:gflags, glog, gtest 。
sudo apt-get install libgflags-dev
sudo apt-get install libgoogle-glog-dev
sudo apt-get install libgtest-dev
如果要删除的话,执行下边的命令
sudo apt-get remove libgflags-dev
sudo apt-get remove libgoogle-glog-dev
sudo apt-get remove libgtest-dev
2.2、源码安装gflags
git clone https://github.com/gflags/gflags.git
cd gflags
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON -DGFLAGS_NAMESPACE=gflags ../
make -j4
sudo make install
注意上面要设置为默认生成动态库,以便glog模块的编译安装。初次编译glog时链接出错,需要把glfag编译成动态库或者使用apt-get方法进行安装。
为了方便学习gflags,查看源码自带的文档以及示例代码,甚至包括源码的实现细节,我们选择源码安装,并将gflags提供的头文件到处到源码方便学习。
cp /usr/local/include/gflags/* ~/learn/vslam/gflags/
3、gflag示例及原理
简单示例:
#include <iostream>
#include <gflags/gflags.h>
using namespace std;
DEFINE_int32(print, 1, "The print times");
DEFINE_string(name, "gflats test!", "The test to argv");
DEFINE_string(msg, "Hello World!", "The message to print");
void print_argv(int argc, char **argv)
{
int iRecycle = 0;
std::cout<<"argc: "<<argc;
for(iRecycle = 0; iRecycle < argc; iRecycle++) {
std::cout<<" "<<argv[iRecycle];
}
std::cout<<std::endl;
}
int main(int argc, char **argv)
{
/* 1、验证ParseCommandLineFlags函数中第三个入参 true or false 的作用 */
print_argv(argc, argv);
/* flags的主要作用,解析命令行中argc和argv参数,并将命令行参数解析结果给我们定义的 print,name,msg三个变量赋值 */
gflags::ParseCommandLineFlags(&argc, &argv, true); //用于接受命令行的flag参数并更新默认参数
print_argv(argc, argv);
/* 2、验证变量的访问 */
for(int i=FLAGS_print; i>0;--i)
std::cout<<"hello word gflags: "<<FLAGS_msg<<std::endl;
gflags::ShutDownCommandLineFlags();
return 0;
}
3.1、gflags功能及示例
定义3个变量
DEFINE_int32(print, 1, "The print times");
DEFINE_string(name, "gflats test!", "The test to argv");
DEFINE_string(msg, "Hello World!", "The message to print");
gflags模块最重要的2个接口
gflags::ParseCommandLineFlags(&argc, &argv, true);
gflags::ShutDownCommandLineFlags();
执行命令及结果
功能: ParseCommandLineFlags
ParseCommandLineFlags 函数会将命令行参数./bin/hello -msg=“gflags valid” -print=3 -name=“ubuntu” 进行解析,并将解析结果给我们定义的三个变量进行赋值。
3.2、参数细节
google::ParseCommandLineFlags(&argc, &argv, true);argc和argv想必大家都很清楚了,说明以下第三个参数的作用:
- 如果设为true,则该函数处理完成后,argv中只保留argv[0],argc会被设置为1。
- 如果为false,则argv和argc会被保留,但是注意函数会调整argv中的顺序,实测并未调整;
顺序有两个:一是变量定义的先后顺序,二是命令行的变量设置顺序
- 第三个参数为true时:google::ParseCommandLineFlags(&argc, &argv, true)执行完后会把解析完成的参数从argv中删除掉,相应argc的值也改为1。测试结果如下:
- 第三个参数为false时:google::ParseCommandLineFlags(&argc, &argv, true)执行完后不会改变argc和argv的值。
注意点;
假如程序中定义了4个参数,命令行输参数值设置了其中2个,那未设置的值为默认值。
可执行程序执行了2次,第一次设置了第1和2个参数,第二次设置第3和4个参数;第二次设置的时候第1和2个参数为默认值,不会保留第一次的值;每次执行可执行程序都是重新运行程序一次,之前的值都不会保留。
4、gflag变量
4.1、七种变量类型
-
gflags头文件: #include <gflags/gflags.h>
-
定义命令行参数
DEFINE_string(<变量名>, <默认值>, <描述>);
比如:DEFINE_string(mystr, "hello", "help for demo");
-
支持的全部数据类型有:
gflags定义类型 描述
DEFINE_bool 布尔类型
DEFINE_int32 32位整型
DEFINE_int64 64位整型
DEFINE_uint64 无符号64位整型
DEFINE_double 浮点类型
DEFINE_string C++ string类型
4.2、变量的程序访问
参数被定义后,可以通过FLAGS_name来访问,比如:
DEFINE_string(msg, “Hello World!”, “The message to print”);
std::cout<<FLAGS_msg<<std::endl;
4.3、全局变量定义
如果过要跨文件调用,则使用全局变量进行处理。
访问在另一个文件中定义的gflags变量:使用DECLARE_,作用类似于extern声明变量。
为了方便管理变量,推荐在*.cpp/.cc文件中DEFINE_变量,在对应的.h文件或单元测试中DECLARE_变量。比如:
// foo.cpp
#include "foo.h"
DEFINE_string(mystr, "hello", "help for demo"); // 定义一个gflags变量name
// foo.h
DECLARE_string(mystr); // extern声明变量name
4.4、命令行赋值方式
- 对于字符或者整数有以下方式:
a.out --hobby="play football"
a.out -hobby="play football"
a.out --hobby "play football"
a.out -hobby "play football"
- 对于布尔变量有下述几种设置方式
a.out --is_handsome
a.out --nois_handsome
a.out --is_handsome=true
a.out --is_handsome=false
- 推荐设置方式
为了使自己的程序易于理解可读,推荐以下命令行设置方式
建议非布尔flag使用这种形式:–variable=value
布尔flag使用这种形式:–variable/–novariable
5、参数合法性检查
#include <iostream>
#include <gflags/gflags.h>
using namespace std;
/* 1、定义变量 */
DEFINE_string(msg, "Hello World!", "The message to print");
DEFINE_string(print, 1, "The times to print");
/* 2、定义参数检查的函数;
此回调函数在 ParseCommandLineFlags 函数中进行回调,检查msg参数是否合理;
此时命令行msg设置的值并未给 FLAGS_msg 进行赋值,检验过之后才会赋值。
入参:
flagname: 为命令行中设置的变量名字:msg;
message: 为命令行中解析出来的参数,此时还未为 FLAGS_msg 赋值
返回值你
参数检查是否成功,失败则提前错误返回,不执行命令;
*/
static bool ValidateMessage(const char* flagname, const std::string &message)
{
std::cout<<"flagname: "<<flagname;
std::cout<<" ValidateMessage: "<<FLAGS_msg<<" updatemessage: "<<message<<std::endl;
return !message.empty();
}
/* 3、为指定的变量注册参数检查回调函数
在 ParseCommandLineFlags 之前注册回调函数才生效;
检查函数是在 ParseCommandLineFlags 函数中进行回调执行;
定义的变量在执行 ParseCommandLineFlags 后才会统一更新;
*/
DEFINE_validator(msg, ValidateMessage);
int main(int argc, char **argv)
{
/* 第二种注册参数检查回调函数的方式,注意一定要在 ParseCommandLineFlags 注册才生效 */
// bool ismesg = RegisterFlagValidator(&FLAGS_msg, ValidateMessage);
gflags::ParseCommandLineFlags(&argc, &argv, false); //用于接受命令行的flag参数并更新默认参数
/* 验证变量的访问 */
for(int i=FLAGS_print; i>0;--i)
std::cout<<"hello gflags: "<<FLAGS_msg<<std::endl;
gflags::ShutDownCommandLineFlags();
return 0;
}
注意回调函数的注册有2种方式,注册一定要在 ParseCommandLineFlags
之前,因为回调函数是在此函数进行回调的,再之后的话回调函数无法被执行。
DEFINE_validator(msg, ValidateMessage);
bool ismesg = RegisterFlagValidator(&FLAGS_msg, ValidateMessage);
关于 DEFINE_validator 的具体定义如下,两种注册的方式本质相同,都是调用 使用RegisterFlagValidator
函数并创建一个静态的bool变量保存函数返回结果。
#define DEFINE_validator(name, validator) \
static const bool name##_validator_registered = \
GFLAGS_NAMESPACE::RegisterFlagValidator(&FLAGS_##name, validator)
从示例函数中需要关注一下几点:
- 检查函数的回调时机:在
ParseCommandLineFlags
函数中执行,在此函数解析到命令行参数,赋值给我们定义的变量之前进行参数检查,检查正确之后才会给定义的变量进行赋值。 - 回调函数注册一定要在
ParseCommandLineFlags
函数之前 - 回调函数入参含义
6、变量赋值(命令行,文件,环境变量)
gflags模块gflags除了和getopt函数一样支持从命令行读取参数,还支持从环境变量和配置文件中读取参数。
并且程序不需要做任何修改,也不需要任何兼容工作就支持从上述三种方式中解析配置给变量赋值。
测试程序见第8节,我们定义了三个变量进行赋值测试:
DEFINE_int32(print, 1, "The print times");
DEFINE_string(name, "gflats test!", "The test to argv");
DEFINE_string(msg, "Hello World!", "The message to print");
6.1、命令行
举例实测
输入命令 ./hello --print=3 --msg="gflags para by cmdline" --name=flags
执行程序,特别注意:
如果字符串中包含空格,则必须使用 ""
双引号括起来,不然只能解析空格前的字符。
有如下打印:
6.2、配置文件
gflags内置环境变量
- –flagfile 从配置文件读取参数值
gflags 中内置了一个名为 flagfile 的 flag,我们可以将所有待赋值的 flag 汇总到一个文件中,并将文件绝对路径赋值给 flagfile,gflags 会将文件中的 flag 与程序中定义的 flag 逐一进行匹配赋值,遇到未定义的 flag 则直接忽略。
–flagfile=my.conf表明要从my.conf文件读取参数的值,文件路径可以使用相对路径或绝对路径。
配置文件格式
配置文件内容如下,配置文件格式和命令行入参格式保持一致,特别注意:
- 变量前必须增加
-
或者--
,不然无法识别; - 对于字符串,不需要增加
""
双引号,不然会当成字符处理;对于命令行中的字符串,如果有空格则一定要用""
双引号,不然无法识别完整。
配置文件具体内容如下:
--print=5
--msg="gflags para by file"
--name="linux"
举例实测
输入命令 ./hello --flagfile=./para.conf
执行程序,有如下打印:
6.3、环境变量
gflags内置环境变量
- –fromenv 从环境变量读取参数值
–fromenv=print,name 表明要从环境变量读取print,name两个参数的值。 - –tryfromenv
与–fromenv类似,当参数的没有在环境变量定义时,不退出(fatal-exit)
举例实测
- 首先通过环境变量指定参数值
export FLAGS_print=7 FLAGS_name=ukylin; 这样程序就可读到print=7,name=ukylin;
env |grep FLAGS 命令验证环境变量是否添加成功。
- 执行可执行程序
输入命令./hello --fromenv=print,name
执行程序,有如下打印:
6.4、gflags内置变量查看
7、自定义版本以及help信息
定制你自己的help信息与version信息:(gflags里面已经定义了-help和-version,你可以通过以下方式定制它们的内容)
version信息:使用google::SetVersionString设定,使用google::VersionString访问
help信息:使用google::SetUsageMessage设定,使用google::ProgramUsage访问
注意:google::SetUsageMessage和google::SetVersionString必须在google::ParseCommandLineFlags之前执行
实测只能使用 --help --version,双横线可以改成单横线;-v或-h都不支持。
8、测试程序
8.1、测试源代码
#include <iostream>
#include <gflags/gflags.h>
using namespace std;
DEFINE_int32(print, 1, "The print times");
DEFINE_string(name, "gflats test!", "The test to argv");
DEFINE_string(msg, "Hello World!", "The message to print");
/* 3、验证检查函数的调用时机 */
static bool ValidateMessage(const char* flagname, const std::string &message)
{
std::cout<<"flagname: "<<flagname;
std::cout<<" ValidateMessage: "<<FLAGS_msg<<" updatemessage: "<<message<<std::endl;
return !message.empty();
}
/*
在 ParseCommandLineFlags 之前注册才生效;
必须执行 ParseCommandLineFlags 才生效;
参数更新也必须执行 ParseCommandLineFlags 才生效
检查函数的执行是在 ParseCommandLineFlags 内部进行回调执行,
此时检查函数val更新了,但是 FLAGS_msg 没有更新。
当 ParseCommandLineFlags 执行完成则所有的 FLAGS_msg 全部更新。
*/
DEFINE_validator(msg, ValidateMessage);
void print_argv(int argc, char **argv)
{
int iRecycle = 0;
std::cout<<"argc: "<<argc;
for(iRecycle = 0; iRecycle < argc; iRecycle++) {
std::cout<<" "<<argv[iRecycle];
}
std::cout<<std::endl;
}
int main(int argc, char **argv)
{
gflags::SetUsageMessage("Test CMake configuration of gflags library (gflags-config.cmake)");
gflags::SetVersionString("0.1");
/* 1、验证ParseCommandLineFlags函数中第三个入参 true or false 的作用 */
print_argv(argc, argv);
gflags::ParseCommandLineFlags(&argc, &argv, true); //用于接受命令行的flag参数并更新默认参数
print_argv(argc, argv);
/* 2、验证变量的访问 */
for(int i=FLAGS_print; i>0;--i)
std::cout<<"print:"<<FLAGS_print<<" name:"<<FLAGS_name<<" msg:"<<FLAGS_msg<<std::endl;
gflags::ShutDownCommandLineFlags();
return 0;
}
8.2、测试代码的CMakeLists.txt
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# 注意包含gflags库的安装路径,一定要放在 ADD_EXECUTABLE 之前
LINK_DIRECTORIES(/usr/local/lib)
ADD_EXECUTABLE(hello main.cpp)
target_link_libraries(hello gflags)
9、其他功能
SetCommandLineOption 接口的用法
判断flags变量是否被用户使用:在gflags.h中,还定义了一些平常用不到的函数和结构体。这里举一个例子,判断参数port有没有被用户设定过
google::CommandLineFlagInfo info;
if(GetCommandLineFlagInfo(“port” ,&info) && info.is_default) {
FLAGS_port = 27015;
}
参数补齐
HandleCommandLineCompletions
最后一点出于一些安全原因或者想要减小可执行文件大小,我们可以去掉自己定义的flag的help信息,如下:
#define STRIP_FLAG_HELP 1 // this must go before the #include!
#include <gflags/gflags.h>
学习资料推荐:
https://blog.youkuaiyun.com/qq_37746927/article/details/122619385
https://gflags.github.io/gflags/