🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
《Qt 方寸极境》 《MySQL》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
前言
一、gflags 介绍
1.1 gflags 核心特性
1.2 gflags 安装
二、gflags 使用
2.1 包含头文件与编译链接
4.2 定义参数
4.3 访问参数
4.4 跨文件访问参数
4.5 初始化解析所有参数
4.6 运行时命令行设置参数
4.6.1 string /int 类型参数
4.6.2 bool 类型参数
4.7 配置文件的使用
4.8 内置特殊参数标识
三、完整示例讲解
3.1 main.cc
3.2 Makefile
3.3 main.conf(配置文件)
3.4 编译与多种运行方式
3.5 跨文件访问参数演示
结束语
前言
在 C++ 服务端开发中,程序启动时经常需要传入端口、监听 IP、日志级别等各类配置参数。如果手动解析
argc/argv,需要自己编写大量字符串判断、类型转换的逻辑,代码繁琐且容易出错。而 gflags 是谷歌开源的 C++ 命令行参数解析库,可以直接定义不同类型的参数,自动完成命令行解析,同时支持配置文件批量加载参数,大幅简化服务启动配置的开发工作。本篇文章将从 gflags 基础特性、环境安装开始,一步步讲解参数定义、参数读取、跨文件访问参数、命令行传参、配置文件加载以及内置参数等核心用法。最后搭配完整工程示例,结合 Makefile 构建脚本,演示多种程序启动方式,帮助你掌握 gflags 在 C++ 项目中的落地使用。
一、gflags 介绍
gflags是谷歌推出的开源 C++ 命令行参数解析库,专门用来完成命令行配置项的声明、定义与解析工作。借助gflags,我们可以很方便地为程序增加各类运行时配置参数,程序启动时通过命令行传入不同 flag,就能动态调整程序行为,无需修改源码重新编译。
1.1 gflags 核心特性
gflags具备下面这些核心特性:
- 使用门槛低:gflags 对外暴露简洁清晰的 API,新增命令行参数只需要一行宏定义,不需要手动解析
argc/argv,新增配置项十分轻松。 - 自动生成帮助文档:定义 flag 时可以附带说明文字,程序运行时支持自动输出帮助信息,使用者可以快速了解每个参数的含义与用法。
- 类型安全:支持布尔、整型、字符串等多种基础类型的配置项,库内部自带类型校验与类型转换逻辑,减少手动类型转换带来的错误。
- 跨平台兼容:可在 Linux、Windows、macOS 多操作系统下编译运行,跨平台项目可以统一使用这套命令行参数方案。
- 良好的可扩展能力:支持开发者自行扩展参数注册、解析逻辑,可根据业务需求做定制化封装。
1.2 gflags 安装
Ubuntu 直接命令安装:
sudo apt-get install libgflags-devCentOS 直接命令安装:
CentOS 系统可以通过 yum 包管理器直接安装开发库:
# CentOS7 / CentOS8 sudo yum install gflags-devel说明:CentOS 的包名后缀是
-devel,和 Ubuntu 的-dev命名习惯不一样。
源码安装(全平台通用,Ubuntu / CentOS 都可以用):
如果软件源自带版本过低,或者需要自定义编译选项,可以源码编译安装:
# 下载源码 git clone https://github.com/gflags/gflags.git # 切换目录 cd gflags/ mkdir build cd build/ # 生成Makefile cmake .. # 编译代码 make # 安装 make install补充提示:源码安装完成后,CentOS 如果出现链接找不到库的问题,需要更新动态链接库缓存:
sudo ldconfig安装完成结果:
二、gflags 使用
2.1 包含头文件与编译链接
想要使用 gflags 库完成命令行参数定义与解析,首先要引入对应的头文件。
#include <gflags/gflags.h>编译程序时,需要手动链接 gflags 库:
g++ main.cc -o main -lgflags为什么编译程序时,需要手动链接 gflags 库?
头文件只提供函数与类型声明,没有具体实现。gflags 是第三方库,它的代码实现存放在libgflags.so库文件中,C++ 编译器不会默认加载第三方库,需要通过-lgflags告知链接器去加载该库,否则链接阶段会报未定义引用错误。
4.2 定义参数
gflags提供一系列宏用来声明命令行参数。这类宏一共接收三项参数:命令行参数名称、参数默认值、该参数的说明文字。
DEFINE_bool(reuse_addr, true, "是否开启网络地址重用选项"); DEFINE_int32(log_level, 1, "日志等级:1-DEBUG,2-WARN,3-ERROR"); DEFINE_string(log_file, "stdout", "日志输出位置设置,默认为标准输出");gflags支持多种基础类型的参数定义,常用宏如下:
DEFINE_bool:布尔类型参数DEFINE_int32:32 位整型参数DEFINE_int64:64 位整型参数DEFINE_uint64:无符号 64 位整型参数DEFINE_double:浮点型参数DEFINE_string:字符串类型参数
4.3 访问参数
参数定义完成后,代码中可以直接使用FLAGS_参数名当作普通全局变量读取参数。
以上面例子为例:
对于reuse_addr这个参数对应的普通全局变量就是FLAGS_reuse_addr;
log_level对应的就是FLAGS_log_level;
log_file对应的就是FLAGS_log_file,即可获取命令行传入的值。
4.4 跨文件访问参数
如果需要在其他源文件使用当前文件定义的 flag,不能重复 DEFINE,需要使用DECLARE_*系列宏做外部声明。
以reuse_addr为例:
DECLARE_bool(reuse_addr);DECLARE 宏本质等价于extern声明(extern FLAGS_reuse_addr),告诉编译器这个全局变量在别的文件中定义,实现跨文件共享参数。
4.5 初始化解析所有参数
参数定义完毕后,需要在main 函数调用解析接口,读取argc/argv,给所有FLAGS_*变量赋值。
int main(int argc, char *argv[]) { google::ParseCommandLineFlags(&argc, &argv, true); return 0; }- argc、argv:就是 main 函数自带的命令行参数。
- 第三个参数remove_flags:
- 设置为
true:解析完成后,会从 argv 中移除已经识别的 flag 参数,同步修改 argc。 - 设置为
false:保留 argv 全部内容,仅调整参数顺序,把识别到的 flag 放到前面。
- 设置为
所以,第三个参数不管是设置为 true 还是 false,都会对我们的 argv 数组进行修改,我们就根据实际需求进行设置即可。
如果我们没有特殊需求的话,这个参数设置哪个都可以,大多数情况只要我们能获取到所需要的参数即可。
4.6 运行时命令行设置参数
程序启动时,可以通过命令行传入 flag,支持长短横线两种写法。
4.6.1 string /int 类型参数
# log_file和"./main.log"用等号连接 ./exec --log_file="./main.log" ./exec -log_file="./main.log" # 也可以不使用等号连接 ./exec --log_file "./main.log" ./exec -log_file "./main.log"4.6.2 bool 类型参数
布尔类型参数用法比较特殊,布尔类型参数不能像上面的string /int 类型参数那样不使用等号连接,而不选择等号连接的写法是:不带值代表开启,--no前缀代表关闭:
./exec --reuse_addr # 等价 true,开启地址重用 ./exec --noreuse_addr # 等价 false,关闭地址重用 ./exec --reuse_addr=true ./exec --reuse_addr=false补充:
--可以终止 flag 解析。例如./exec -f1 1 -- -f2 2,-f1会被识别为 flag,-f2不会被 gflags 解析,作为普通业务参数。
针对上面不管是 string /int 类型参数 还是 bool 类型参数,我们都推荐的写法是:
./exec --log_file="./main.log" ./exec --reuse_addr=true因为这样我们就不需要对两种参数的写法进行区分了,统一都是这种形式,而这种形式我们也可以理解为是一种Key-Value 的形式,便于我们记忆使用。
4.7 配置文件的使用
当参数数量很多,每次启动手动输入命令行参数会很麻烦。gflags 支持从配置文件批量加载参数,一次编写配置文件,后续直接复用。
注意:配置文件内的参数名必须和代码 DEFINE 定义的名字完全一致,字符串值不需要加双引号。
示例 main.conf:
-reuse_addr=true -log_level=3 -log_file=./log/main.log启动时通过--flagfile指定配置文件路径:
./exec --flagfile=main.conf4.8 内置特殊参数标识
gflags自带内置 flag,无需自己定义,直接命令行使用:
--help:打印所有参数的简要帮助信息--helpfull:输出完整详细的帮助文档--helpshort:仅打印当前文件内定义的参数--helpxml:以 xml 格式输出帮助信息,便于程序自动化处理--version:打印程序版本信息--flagfile:从指定文件读取命令行配置
举例:
./exec --help说明:
--help、--helpfull、--helpshort、--helpxml、--version都是查看类指令,执行后程序打印信息就直接退出,不会执行业务逻辑。
三、完整示例讲解
3.1 main.cc
/* gflags使用: 1、包含头文件 <gflags/gflags.h> 2、定义要捕获的参数信息,以及设置默认值 3、解析参数,初始化参数数据 4、获取数据 */ #include <iostream> #include <gflags/gflags.h> #include <string> //定义gflags参数 DEFINE_bool(reuse_addr, true, "是否启用地址复用选项"); DEFINE_int32(listen_port, 8080, "服务器监听端口"); DEFINE_string(listen_ip, "0.0.0.0", "服务器监听IP"); DEFINE_double(pi, 3.14, "圆周率"); int main(int argc, char *argv[]) { //解析命令行参数 google::ParseCommandLineFlags(&argc, &argv, true); //解析完参数,进行获取(注意:获取的是变量,变量名称是参数名称加上 FLAGS_ 前缀) std::cout << FLAGS_reuse_addr << std::endl; std::cout << FLAGS_listen_port << std::endl; std::cout << FLAGS_listen_ip << std::endl; std::cout << FLAGS_pi << std::endl; return 0; }3.2 Makefile
main:main.cc g++ -o $@ $^ -lgflags .PHONY:clean clean: rm -rf main$@:目标文件(main)$^:所有依赖文件(main.cc)-lgflags:链接 gflags 第三方库
3.3 main.conf(配置文件)
-reuse_addr=false -listen_port=8888 -listen_ip=127.0.0.1 -pi=3.14159263.4 编译与多种运行方式
- 编译
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ make g++ -o main main.cc -lgflags- 使用默认参数运行(不传递任何 flag)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main 1 8080 0.0.0.0 3.14- 命令行传参,覆盖默认值
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --reuse_addr=false --listen_port=8888 --listen_ip=127.0.0.1 --pi=3.1415 0 8888 127.0.0.1 3.1415这里我们会发现如果直接通过命令行传参的形式修改默认值,写起来是非常繁琐的,我们需要对每个 flags 参数都进行重新赋值,这也是为什么有配置文件的存在:通过配置文件加载参数,我们只需要写一项就可以将所有我们需要访问的参数都进行设置。
- 通过配置文件加载参数
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf 0 8888 127.0.0.1 3.14159不管我们定义了多少个 gflags 参数,只需要把这些参数都设置到配置文件中,通过--flagfile=main.conf,我们就不再需要一个一个在命令行中进行设置了。
- 配置文件 + 命令行参数(命令行优先级更高,覆盖配置文件)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --flagfile=main.conf --listen_port=9090 0 9090 127.0.0.1 3.14159- 查看帮助信息(内置 flag)
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main --help main: Warning: SetUsageMessage() never called Flags from ./src/gflags.cc: -flagfile (load flags from file) type: string default: "" -fromenv (set flags from the environment [use 'export FLAGS_flag1=value']) type: string default: "" -tryfromenv (set flags from the environment if present) type: string default: "" -undefok (comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name. IMPORTANT: flags in this list that have arguments MUST use the flag=value format) type: string default: "" Flags from ./src/gflags_completions.cc: -tab_completion_columns (Number of columns to use in output for tab completion) type: int32 default: 80 -tab_completion_word (If non-empty, HandleCommandLineCompletions() will hijack the process and attempt to do bash-style command line flag completion on this value.) type: string default: "" Flags from ./src/gflags_reporting.cc: -help (show help on all flags [tip: all flags can have two dashes]) type: bool default: false currently: true -helpfull (show help on all flags -- same as -help) type: bool default: false -helpmatch (show help on modules whose name contains the specified substr) type: string default: "" -helpon (show help on the modules named by this flag value) type: string default: "" -helppackage (show help on all modules in the main package) type: bool default: false -helpshort (show help on only the main module for this program) type: bool default: false -helpxml (produce an xml version of help) type: bool default: false -version (show version and build info and exit) type: bool default: false Flags from main.cc: -listen_ip (服务器监听IP) type: string default: "0.0.0.0" -listen_port (服务器监听端口) type: int32 default: 8080 -pi (圆周率) type: double default: 3.1400000000000001 -reuse_addr (是否启用地址复用选项) type: bool default: true代码要点小结:
DEFINE_*在全局域定义参数,不能写在函数内部。google::ParseCommandLineFlags放在 main 开头,解析后才能读取FLAGS_*变量。- 配置文件参数不需要引号,参数名和 DEFINE 保持一致。
- 优先级:命令行参数 > 配置文件参数 > 代码内默认值。
3.5 跨文件访问参数演示
child.h:
extern void Print();child.cc(错误写法):
#include <iostream> #include <gflags/gflags.h> void Print() { //跨文件访问gflags参数 std::cout << FLAGS_listen_ip << std::endl; }main.cc:
#include <iostream> #include <gflags/gflags.h> #include <string> #include "child.h" //定义gflags参数 DEFINE_bool(reuse_addr, true, "是否启用地址复用选项"); DEFINE_int32(listen_port, 8080, "服务器监听端口"); DEFINE_string(listen_ip, "0.0.0.0", "服务器监听IP"); DEFINE_double(pi, 3.14, "圆周率"); int main(int argc, char *argv[]) { //解析命令行参数 google::ParseCommandLineFlags(&argc, &argv, true); //解析完参数,进行获取(注意:获取的是变量,变量名称是参数名称加上 FLAGS_ 前缀) std::cout << FLAGS_reuse_addr << std::endl; std::cout << FLAGS_listen_port << std::endl; std::cout << FLAGS_listen_ip << std::endl; std::cout << FLAGS_pi << std::endl; Print(); return 0; }Makefile:
main:main.cc child.cc g++ -o $@ $^ -lgflags .PHONY:clean clean: rm -rf main运行错误结果演示:
child.cc(正确写法):
#include <iostream> #include <gflags/gflags.h> DECLARE_string(listen_ip); void Print() { //跨文件访问gflags参数 std::cout << FLAGS_listen_ip << std::endl; }运行结果演示:
[ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ make g++ -o main main.cc child.cc -lgflags [ply@iZbp12ear9ufvimc78fddkZ gflags_test]$ ./main 1 8080 0.0.0.0 3.14 0.0.0.0最后我们再拿曾经基于UDP协议写的一个Echo回显服务器,对代码进行修改,加上 gflags 第三方库,我们就可以直观感受到 gflags 带来的效果:
UdpServer.cc:
#include "UdpServer.hpp" #include <iostream> #include <cstdio> #include <memory> #include <gflags/gflags.h> DEFINE_int32(port, 8080, "端口号"); // ./udpserver port int main(int argc, char *argv[]) { // if (argc != 2) // { // std::cerr << "Usage: " << argv[0] << " port" << std::endl; // return 1; // } // gflags 替代了手动检查 argc google::ParseCommandLineFlags(&argc, &argv, true); ENABLE_CONSOLE_LOG_STRATEGY(); // uint16_t port = std::stoi(argv[1]); // 字符串转成uint16_t,获取客户传入的端口号 // 无需手动解析 argv,无需处理 char* 到其他类型的转换 // 类型自动转换:它内部会根据你定义的宏(_int32, _double, _string 等), // 自动把命令行传进来的字符串(char*)转换成对应的 C++ 类型 uint16_t port = FLAGS_port; std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, calculate_handler); usvr->Init(); usvr->Start(); return 0; }UdpClient.cc:
// 各自头文件包含... #include <gflags/gflags.h> DEFINE_string(ip, "127.0.0.1", "IP地址"); DEFINE_int32(port, 8080, "端口号"); // ./udpclient ip port int main(int argc, char *argv[]) { // if (argc != 3) // { // std::cerr << "Usage: " << argv[0] << " ip port" << std::endl; // return 1; // } google::ParseCommandLineFlags(&argc, &argv, true); // 1、创建socket套接字 int socketfd = socket(AF_INET, SOCK_DGRAM, 0); if (socketfd < 0) { std::cerr << "socket created error" << std::endl; return 2; } // 填写服务器信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // server.sin_port = htons(std::stoi(argv[2])); // server.sin_addr.s_addr = inet_addr(argv[1]); server.sin_port = htons(FLAGS_port); server.sin_addr.s_addr = inet_addr(FLAGS_ip.c_str()); // 读写IO处理... return 0; }Makefile:
.PHONY:all all:udpserver udpclient udpserver:UdpServer.cc g++ -o $@ $^ -std=c++17 -lgflags udpclient:UdpClient.cc g++ -o $@ $^ -std=c++17 -lgflags .PHONY:clean clean: rm -rf udpserver udpclient运行结果展示:
结束语
到这里,gflags 的基础用法与工程实战就全部讲解完毕。我们从库的特性、安装入手,依次学习了参数定义、参数访问、跨文件共享参数、命令行传参、配置文件加载以及内置特殊标识等核心能力,并通过一套完整示例工程,演示了编译、多种运行方式的实际效果。
gflags 凭借简洁的 API、多类型参数支持,成为 C++ 后端项目中非常常用的命令行配置工具。需要注意的是,gflags 更适合存放程序启动时的静态配置,不适合运行时动态频繁修改业务参数。
后续你可以在自己的网络服务项目中集成 gflags,把端口、IP、日志开关等配置交由它管理,替代硬编码,提升项目的灵活性。