0%

使用 gflags 进行 C++ 命令行参数处理

目录

使用 gflags 进行 C++ 命令行参数处理

0 前言

gflags 是由 Google 开发维护的 C++ 命令行参数解析库,在百度 Apollo 自动驾驶框架中被广泛用于各模块内部的命令行参数处理。本文简要介绍了 gflags 的安装和使用方法。

1 安装

本文中采用源码编译的方式进行 gflags 的安装。以 v2.2.2 版本为例,依次执行下述命令即可完成安装:

1
2
3
4
5
6
7
wget https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz
tar -xzvf v2.2.2.tar.gz
cd gflags-2.2.2/
mkdir build && cd build
cmake ..
make
sudo make install

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
2
3
4
5
6
7
8
9
10
// flags.hpp
#ifndef FLAGS_HPP_
#define FLAGS_HPP_

#include "gflags/gflags.h"

DECLARE_int32(demo_flag_int32);
DECLARE_bool(demo_flag_bool);

#endif

上述代码段声明了一个 int32 类型的 flag demo_flag_int32 和一个 bool 类型的 flag demo_flag_bool

2.2 flag 的定义

在包含了 flag 的声明头文件后,我们通过形如 DEFINE_type 的宏进行 flag 的定义:

1
2
3
4
5
// flags.cpp
#include "../inc/flags.hpp"

DEFINE_int32(demo_flag_int32, 0, "A int32 demo flag.");
DEFINE_bool(demo_flag_bool, false, "A bool demo flag.");

上述代码段为 demo_flag_int32demo_flag_bool 定义了初值和帮助说明信息字符串(类似变量注释)。

完成 flag 的定义后,gflags 将自动为 flag 生成形如 FLAGS_name 的全局变量,例如上文中 demo_flag_int32demo_flag_bool 对应的全局变量分别为 FLAGS_demo_flag_int32FLAGS_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
2
3
4
./bin/gflags_demo --demo_flag_int32=10  # 双虚线 + 赋值符
./bin/gflags_demo -demo_flag_int32=10 # 单虚线 + 赋值符
./bin/gflags_demo --demo_flag_int32 10 # 双虚线 + 空格符
./bin/gflags_demo -demo_flag_int32 10 # 单虚线 + 空格符

对于 bool 类型的 flag 而言,情况略有不同。bool 类型的 flag 同样支持“双/单虚线 + 赋值符”的赋值形式,但不支持“双/单虚线 + 空格符”的赋值形式。此外,bool 类型的 flag 还支持下面两种特殊赋值形式:

1
2
./bin/gflags_demo --demo_flag_bool    # 只使用 flag 名,赋值为 true
./bin/gflags_demo --nodemo_flag_bool # flag 名前添加 no 前缀,赋值为 false

最佳实践: 对于非 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
2
3
# flagfile.conf
--demo_flag_int32=20
--nodemo_flag_bool

我们甚至可以在 flagfile.conf 中嵌套包含其它的命令行参数配置文件:

1
2
3
4
# flagfile.conf
--flagfile=/tmp/myflags.conf
--demo_flag_int32=20
--nodemo_flag_bool

然后,我们可以通过 2.3.1 中命令行的方式将 flagfile.conf 的绝对路径赋值给 flagfile

1
./bin/gflags_demo --flagfile=/home/shipeng/Desktop/gflags_demo/flagfile.conf

或在程序中通过 google::SetCommandLineOption 函数加载 flagfile.conf

1
2
3
4
// get flags from a flag file, absolute path of the flag file is needed
std::string flag_file_path =
"/home/shipeng/Desktop/gflags_demo/flagfile.conf";
google::SetCommandLineOption("flagfile", flag_file_path.c_str());

2.3.3 通过变量对 flag 进行赋值

上文中我们已经提到,gflags 会为定义好的 flag 自动生成可正常读写的全局变量,顺其自然地我们可以在代码中通过生成的变量直接对 flag 进行赋值:

1
2
3
// modify the flag by manual within code
FLAGS_demo_flag_int32 = 30;
FLAGS_demo_flag_bool = true;

2.3.4 自动校验 flag 的值

需要提及的是,在完成 flag 的定义后,紧接着我们可以通过 DEFINE_validator 宏为已经定义好的 flag 注册一个校验函数。为使代码尽量简洁,这里的校验函数我们使用了一个匿名 lambda 表达式:

1
2
3
4
5
6
7
8
9
10
// flags.cpp
#include "../inc/flags.hpp"

DEFINE_int32(demo_flag_int32, 0, "A int32 demo flag.");
DEFINE_bool(demo_flag_bool, false, "A bool demo flag.");

DEFINE_validator(demo_flag_int32,
[](const char *flag_name, int32_t flag_value) -> bool {
return flag_value < 25;
});

DEFINE_validator 宏的第一参数是 flag 的名称,第二参数是校验函数,DEFINE_validator 通过调用 google::RegisterFlagValidator 函数将传入的 flag 和校验函数进行绑定,后续我们通过命令行或 google::SetCommandLineOption 函数对 flag 进行赋值时,gflags 会自动调用校验函数对赋给 flag 的值进行检查,不满足条件的值将触发程序抛出异常。值得注意的是,通过变量对 flag 进行直接赋值并不在校验范围内。

下图直观地展示了本文第 2 章节的相关结论:

gflags C++ 工程示例主要代码及程序运行结果

可以看到,当我们通过命令行为 demo_flag_int32 赋值 100 时触发了“值校验失败”的异常,当我们指定未定义 flag demo_flag 时触发了“未知命令行 flag”的异常。

参考

  1. How To Use gflags (formerly Google Commandline Flags)
  2. glags - GitHub
  3. Compiling the glags source code with CMake

Thank you for your donate!