目录
0 前言
gflags 是由 Google 开发维护的 C++ 命令行参数解析库,在百度 Apollo 自动驾驶框架中被广泛用于各模块内部的命令行参数处理。本文简要介绍了 gflags 的安装和使用方法。
1 安装
本文中采用源码编译的方式进行 gflags 的安装。以 v2.2.2 版本为例,依次执行下述命令即可完成安装:
1 | wget https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz |
2 使用
本文中使用的 gflags C++ 工程 Demo 可点击这里下载,在工程根目录下运行 build.sh
脚本可执行编译(首次运行可能需要为脚本添加可执行权限)。需要注意的是,gflags 默认依赖 pthread 库,所以在编译工程时需要同时将 pthread 链接到主程序:
1 | target_link_libraries(${PROJECT_NAME} gflags pthread) |
gflags 的基本使用包括 flag 的声明、定义、读写、校验,下面依次进行介绍。
2.1 flag 的声明
一般而言,我们的命令行参数需要在多个文件中被使用,所以通用的工程实践是:
- a) 在
.h
.hpp
头文件中进行 flag 的声明 - b) 在
.cc
.cpp
源文件中进行 flag 的定义 - c) 其它文件包含声明了 flag 的头文件,并对 flag 进行读写操作
目前,gflags 只支持以下几种基本数据类型:
bool
:布尔类型int32
:32 位有符号整数类型int64
:64 位有符号整数类型uint32
:32 位无符号整数类型uint64
:64 位无符号整数类型double
:双精度浮点类型string
:C++ 字符串类型
我们通过形如 DECLARE_type
的宏进行 flag 的声明:
1 | // flags.hpp |
上述代码段声明了一个 int32
类型的 flag demo_flag_int32
和一个 bool
类型的 flag demo_flag_bool
。
2.2 flag 的定义
在包含了 flag 的声明头文件后,我们通过形如 DEFINE_type
的宏进行 flag 的定义:
1 | // flags.cpp |
上述代码段为 demo_flag_int32
和 demo_flag_bool
定义了初值和帮助说明信息字符串(类似变量注释)。
完成 flag 的定义后,gflags 将自动为 flag 生成形如 FLAGS_name
的全局变量,例如上文中 demo_flag_int32
和 demo_flag_bool
对应的全局变量分别为 FLAGS_demo_flag_int32
和 FLAGS_demo_flag_bool
。生成的全局变量与一般变量无异,可正常进行读写操作。
2.3 读写 flag
gflags 中包含三种对 flag 的赋值操作:命令行赋值、flagfile 赋值和变量赋值。
2.3.1 通过命令行对 flag 进行赋值
我们需要在可执行程序的入口函数 int main(int argc, char *argv[])
中添加如下语句来使能 gflags 解析传入可执行程序的命令行参数:
1 | google::ParseCommandLineFlags(&argc, &argv, true); |
然后在运行可执行程序时为我们定义好的 flag 指定一个值即可。以上文中定义的 demo_flag_int32
为例,对于非 bool
类型的 flag,我们可以通过在终端中执行下述命令行中的任意一种为其赋予新值 10:
1 | ./bin/gflags_demo --demo_flag_int32=10 # 双虚线 + 赋值符 |
对于 bool
类型的 flag 而言,情况略有不同。bool
类型的 flag 同样支持“双/单虚线 + 赋值符”的赋值形式,但不支持“双/单虚线 + 空格符”的赋值形式。此外,bool
类型的 flag 还支持下面两种特殊赋值形式:
1 | ./bin/gflags_demo --demo_flag_bool # 只使用 flag 名,赋值为 true |
最佳实践: 对于非
bool
类型的 flag,使用--flag_name=flag_value
的形式进行赋值;对于bool
类型的 flag,使用--flag_name
和--noflag_name
的形式进行赋值。此种实践可以增强代码可读性。
需要注意的是,如果命令行中被赋值的 flag 未被定义,将触发程序抛出异常。
2.3.2 通过 flagfile 对 flag 进行赋值
假如我们有很多命令行参数需要传入可执行程序,2.3.1 中命令行的方式将变得过于繁琐,是否有更加简洁的方式呢?
gflags 中内置了一个名为 flagfile
的 flag,我们可以将所有待赋值的 flag 汇总到一个文件中,并将文件绝对路径赋值给 flagfile
,gflags 会将文件中的 flag 与程序中定义的 flag 逐一进行匹配赋值,遇到未定义的 flag 则直接忽略。具体步骤:
首先,我们需要创建一个命令行参数配置文件(这里我们命名为 flagfile.conf
),将所有 flag 写入其中,并依次赋值:
1 | # flagfile.conf |
我们甚至可以在 flagfile.conf
中嵌套包含其它的命令行参数配置文件:
1 | # flagfile.conf |
然后,我们可以通过 2.3.1 中命令行的方式将 flagfile.conf
的绝对路径赋值给 flagfile
:
1 | ./bin/gflags_demo --flagfile=/home/shipeng/Desktop/gflags_demo/flagfile.conf |
或在程序中通过 google::SetCommandLineOption
函数加载 flagfile.conf
:
1 | // get flags from a flag file, absolute path of the flag file is needed |
2.3.3 通过变量对 flag 进行赋值
上文中我们已经提到,gflags 会为定义好的 flag 自动生成可正常读写的全局变量,顺其自然地我们可以在代码中通过生成的变量直接对 flag 进行赋值:
1 | // modify the flag by manual within code |
2.3.4 自动校验 flag 的值
需要提及的是,在完成 flag 的定义后,紧接着我们可以通过 DEFINE_validator
宏为已经定义好的 flag 注册一个校验函数。为使代码尽量简洁,这里的校验函数我们使用了一个匿名 lambda 表达式:
1 | // flags.cpp |
DEFINE_validator
宏的第一参数是 flag 的名称,第二参数是校验函数,DEFINE_validator
通过调用 google::RegisterFlagValidator
函数将传入的 flag 和校验函数进行绑定,后续我们通过命令行或 google::SetCommandLineOption
函数对 flag 进行赋值时,gflags 会自动调用校验函数对赋给 flag 的值进行检查,不满足条件的值将触发程序抛出异常。值得注意的是,通过变量对 flag 进行直接赋值并不在校验范围内。
下图直观地展示了本文第 2 章节的相关结论:
可以看到,当我们通过命令行为 demo_flag_int32
赋值 100 时触发了“值校验失败”的异常,当我们指定未定义 flag demo_flag
时触发了“未知命令行 flag”的异常。
参考
- How To Use gflags (formerly Google Commandline Flags)
- glags - GitHub
- Compiling the glags source code with CMake