Github
官方文档
Gflags简明教程 http://dreamrunner.org/blog/2014/03/09/gflags-jian-ming-shi-yong/
GFlags使用文档 http://www.yeolar.com/note/2014/12/14/gflags/
简介
GFlags是Google开源的一套命令行参数处理的开源库,包括C++的版本和python 版本。和 getopt() 之类的库不同,flag的定义可以散布在各个源码中,而不用放在一起。一个源码文件可以定义一些它自己的flag,链接了该文件的应用都能使用这些flag。这样就能非常方便地复用代码。如果不同的文件定义了相同的flag,链接时会报错。
定义Flags
定义一个flag是简单的:只需要使用你想用的类型的相应的宏就可以。
使用flags需要包含头文件 :
#include <gflags/gflags.h>
example:
// foo.cc
#include <gflags/glags.h>
DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german",
"comma-separated list of languages to offer in the 'lang' menu");
示例代码分别定义了一个bool和一个string类型的参数,该宏的三个参数含义分别为命令行参数名,参数默认值,以及参数的帮助信息。gflag不支持列表,用户通过灵活借助string参数实现,比如上述的languages参数,可以类型为string,但可看作是以逗号分割的参数列表。
支持的类型:
- DEFINE_bool: boolean
- DEFINE_int32: 32-bit integer
- DEFINE_int64: 64-bit integer
- DEFINE_uint64: unsigned 64-bit integer
- DEFINE_double: double
- DEFINE_string: C++ string
DEFINE宏包含三个参数:flag名、默认值、描述方法的帮助。帮助会在执行 --help flag时显示。
可以在任何源文件中定义flag,但是每个只能定义一次。如果需要在多处使用,那么在一个文件中 DEFINE ,在其他文件中 DECLARE 。比较好的方法是在 .cc 文件中 DEFINE ,在 .h 文件中 DECLARE ,这样包含头文件即可使用flag了。
使用flag访问参数
当参数被定义后,通过FLAGS_name就可访问到对应的参数,定义的flag可以像正常的变量一样使用,只需在前面加上 FLAGS_前缀。如前面例子中的定义了 FLAGS_big_menu 和 FLAGS_languages两个变量。可以像一般变量一样读写:
if (FLAGS_consider_made_up_languages)
FLAGS_languages += ",klingon"; // implied by --consider_made_up_languages
if (FLAGS_languages.find("finnish") != string::npos)
HandleFinnish();
以上的访问方式,仅在参数定义和访问在同一个文件(或是通过头文件包含)时,FLAGS_name才能访问到参数,如果要访问其他文件里定义的参数,则需要使用DECLARE_type。比如在foo.cc中DEFINE_string(color, “red”, “the color you want to use”); 这时如果你需要在foo_test.cc中使用color这个参数,你需要加入DECLARE_string(color, “red”, “the color you want to use”);
参数检查
定义参数后,可以给参数注册一个检查函数(validator),当从命令行指定参数或通过SetCommandLineOption()指定参数时,检查函数就会被调用,两个参数分别为命令行参数名,以及设置的参数值
static bool ValidatePort(const char* flagname, int32 value) {
if (value > 0 && value < 32768) // value is ok
return true;
printf("Invalid value for --%s: %d\n", flagname, (int)value);
return false;
}
DEFINE_int32(port, 0, "What port to listen on");
static const bool port_dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort)
建议在定义参数后,立即注册检查函数。RegisterFlagValidator()在检查函数注册成功时返回true;如果参数已经注册了检查函数,或者检查函数类型不匹配,返回false。
初始化所有参数
在引用程序的main()里通过 google::ParseCommandLineFlags(&argc, &argv, true); 即完成对gflags参数的初始,其中第三个参数为remove_flag,如果为true,gflags会移除parse过的参数,否则gflags就会保留这些参数,但可能会对参数顺序进行调整。 比如 “/bin/foo” “arg1” “-q” “arg2” 会被调整为 “/bin/foo”, “-q”, “arg1”, “arg2”,这样更好理解。
#include <iostream>
#include <gflags/gflags.h>
DEFINE_bool(isvip, false, "If Is VIP");
DEFINE_string(ip, "127.0.0.1", "connect ip");
DECLARE_int32(port);
DEFINE_int32(port, 80, "listen port");
int main(int argc, char** argv)
{
google::ParseCommandLineFlags(&argc, &argv, true); //初始化所有参数
std::cout<<"ip:"<<FLAGS_ip<<std::endl;
std::cout<<"port:"<<FLAGS_port<<std::endl;
if (FLAGS_isvip)
{
std::cout<<"isvip:"<<FLAGS_isvip<<std::endl;
}
google::ShutDownCommandLineFlags();
return 0;
}
链接时使用 -gflags ,运行使用 ./gflags -ip=“211.152.52.106” -port=8080 -isvip=true ,但是很遗憾,使用 valgrind 检测有内存泄漏。
输出结果如下:
ip:211.152.52.106
port:8080
isvip:1
在命令行指定参数
比如要在命令行指定languages参数的值,可通过如下4种方式,int32, int64等类型与string类似。
app_containing_foo --languages=“chinese,japanese,korean”
app_containing_foo -languages=“chinese,japanese,korean”
app_containing_foo --languages “chinese,japanese,korean”
app_containing_foo -languages “chinese,japanese,korean”
对于bool类型,则可通过如下几种方式指定参数
app_containing_foo --big_menu
app_containing_foo --nobig_menu
app_containing_foo --big_menu=true
app_containing_foo --big_menu=false
特殊参数
–help 打印定义过的所有参数的帮助信息
–version 打印版本信息 通过google::SetVersionString()指定
–nodefok 但命令行中出现没有定义的参数时,并不退出(error-exit)
–fromenv 从环境变量读取参数值 --fromenv=foo,bar表明要从环境变量读取foo,bar两个参数的值。通过export FLAGS_foo=xxx; export FLAGS_bar=yyy 程序就可读到foo,bar的值分别为xxx,yyy。
–tryfromenv 与–fromenv类似,当参数的没有在环境变量定义时,不退出(fatal-exit)
–flagfile 从文件读取参数值,–flagfile=my.conf表明要从my.conf文件读取参数的值。在配置文件中指定参数值与在命令行方式类似,另外在flagfile里可进一步通过–flagfile来包含其他的文件。
Caffe实例解析
以 tools/caffe.cpp 为例分析gflags使用方法
在tools/caffe.cpp中首先包含了gflags的头文件
#include <gflags/gflags.h>
命令行参数传递
接下来定义了一些需要从命令行传递参数的变量
DEFINE_string(gpu, "",
"Optional; run in GPU mode on given device IDs separated by ','."
"Use '-gpu all' to run on all available GPUs. The effective training "
"batch size is multiplied by the number of devices.");
DEFINE_string(solver, "",
"The solver definition protocol buffer text file.");
DEFINE_string(model, "",
"The model definition protocol buffer text file.");
DEFINE_string(phase, "",
"Optional; network phase (TRAIN or TEST). Only used for 'time'.");
DEFINE_int32(level, 0,
"Optional; network level.");
......
在main函数中调用的GlobalInit()函数中调用了ParseCommandLineFlags()
// Google flags.
::gflags::ParseCommandLineFlags(pargc, pargv, true);
这个函数的作用就是解析命令行参数。
pargc,pargv为命令行传递的参数个数和参数表,第三个参数作用为:
值 | 作用 |
---|---|
true | 函数处理完成后,argv中只保留argv[0],argc会被设置为1 |
false | argv和argc会被保留,但是注意函数会调整argv中的顺序。 |
执行完这参数后,就可以用FLAGS_xxx访问变量了。
例如caffe.cpp中, FLAGS_solve表示命令行参数传递的模型文件,FLAGS_weights表示令行参数传递的权值文件
// Train / Finetune a model.
int train() {
CHECK_GT(FLAGS_solver.size(), 0) << "Need a solver definition to train.";
CHECK(!FLAGS_snapshot.size() || !FLAGS_weights.size())
<< "Give a snapshot to resume training or weights to finetune "
"but not both.";
帮助信息
gflags::SetUsageMessage函数用于设置命令行帮助信息。
// Usage message.
gflags::SetUsageMessage("command line brew\n"
"usage: caffe <command> <args>\n\n"
"commands:\n"
" train train or finetune a model\n"
" test score a model\n"
" device_query show GPU diagnostic information\n"
" time benchmark model execution time");
设置帮助信息后,当参数错误或加 -help选项是可以打印帮助信息
./build/tools/caffe
caffe: command line brew
usage: caffe <command> <args>
commands:
train train or finetune a model
test score a model
device_query show GPU diagnostic information
time benchmark model execution time
...
版本信息
gflags::SetVersionString用于设置版本信息
// Set version
gflags::SetVersionString(AS_STRING(CAFFE_VERSION));
这里的CAFFE_VERSION是在Makefile中定义的
当命令行参数加 -version 时会打印版本信息
./build/tools/caffe -version
caffe version 1.0.0-rc3
demo
//demo_gflags.cc
#include "gflags/gflags.h"
DEFINE_string(server_ip, "127.0.0.1", "server ip");
DEFINE_int32(server_port, 8080, "server port");
// demo.cc
#include <iostream>
#include "gflags/gflags.h"
DEFINE_bool(test_bool, false, "test bool value");
DEFINE_int32(test_int32, 666, "test int value");
DECLARE_string(server_ip);
DECLARE_int32(server_port);
int main(int argc, char** argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
// for (int i = 0; i < argc; i++) {
// std::cout << argv[i] << std::endl;
// }
if (FLAGS_test_bool) {
std::cout << "test_bool is true." << std::endl;
std::cout << "test_int32: " << FLAGS_test_int32 << std::endl;
}
std::cout << "server ip: " << FLAGS_server_ip
<< " , server port: " << FLAGS_server_port << std::endl;
return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
project(gflag)
find_package(gflags REQUIRED)
add_executable(${PROJECT_NAME}_demo demo.cc demo_gflags.cc)
target_link_libraries(${PROJECT_NAME}_demo ${GFLAGS_LIBRARIES})
//demo.gflags
-server_ip="192.168.0.123"
-server_port=8899
编译
$ cd gflag
$ mkdir build
$ cmake ..
$ make
执行
# 不加命令行
$ ./gflag_demo
# 加命令行变量控制 `-test_bool=true` test_bool为程序开头定义的标志
$ ./gflag_demo -test_bool=true -test_int32=666
# 加命令行文件输入 `-flagfile=filename` 注意路径, - 和 -- 一样
$ ./gflag_demo -flagfile=../demo.gflags ##文件绝对路径
# 其他
# -fromenv=value 从环境中读取value
# --tryfromenv=value 环境中是否未定义不是致命错误
gflags使用规范
任何好用的工具如果使用不当都会带来不好的后果,gflags也是一样。我遇到过一些gflags的“坑”,还从领导和同事那里获得一些好的想法,整理成7条gflags使用规范。有意识的遵循这些规范,对项目的开发维护和自身的技术成长都将有很大的益处。
- 规范1:bool类型的gflags默认值设置成false,防止误启用新功能。
新的功能上线一定要经过代码审查、测试和验证流程,默认为true的gflags风险太大。 - 规范2:应定时清理旧的gflags。
随着时间的流逝,代码里的gflags会越来越多,当你的工程代码里包含成百上千行的gflags时,阅读和维护代码的体验简直是太过酸爽。非常有必要定时删除代码中旧的gflags,根据其开关打开(true)和关闭(false)情况来删除gflags及其相关代码。 - 规范3:清理旧的gflags时应同时删除相应的gflags配置,以保证线上配置的整洁。
配置文件和代码保持同步是一种非常好的开发和维护体验。 - 规范4:if语句中应尽量避免gflags参与逻辑运算。
当if语句中出现与gflags相关的与、或、非逻辑运算时,事情就会变得复杂起来,gflags的开关状态不再是唯一的决定因素,代码阅读和删除gflags也会变得十分困难。我曾经删错过一个旧的gflags,幸运的是在CR阶段(CodeReview,代码审查)被细心的同事指出,避免了一次踩坑。这是那段令我难忘的代码的样子:
在这个例子中同时出现了与、或、非这3个逻辑运算,它是工程中真实存在的代码,绝非由我杜撰。此时gflags a、b、c、d的值都是false,现在的任务是删除这些旧的gflags和它们包住的代码,应该保留哪部分代码呢?如果你和我一样忘记了或运算||和与运算&&谁的优先级更高,那么掉到坑里的概率非常大。言归正传,我们有很多方法避免这样的代码出现,gflags绝不应该参与复杂的逻辑运算。 - 规范5:公共模块的gflags应尽快删除。
公共模块的gflags在上线运行一段时间后,应尽快删除,理想的情况是公共模块都没有gflags包含。这样做的理由是使用公共模块往往不知道这些gflags的存在,非常容易留坑。 - 规范6:不要在单元测试代码中使用gflags。
如果UT(UnitTest,单元测试)代码里用到了gflags,情况会变得复杂,在删除旧的gflags时需要同步修改单元测试代码,否则会导致测试失败,jenkins上的任务会变红(即测试失败)。因此,最好不要在单元测试代码里使用gflags。 - 规范7:提交代码时,应记录新增或删除的gflags配置。
这样的好处是方便测试的同事进行测试,这样利人利己的规范是非常值得遵守的。
最后,我把这7条规范总结并整理成一张图片,欢迎大家留言补充更多、更好的gflags使用规范。
- 金句分享
人的天性之一,就是不会接受别人的批评,总是认为自己永远是对的,喜欢找各种各样的借口为自己辩解。
——出自《人性的弱点》,戴尔·卡耐基(Dale Carnegie),美国著名人际关系学大师。
解读:永远不要批评别人,因为指责只不过是在浪费自己和他人的时间,应该换种方式去沟通和解决问题。