GFlags使用文档

Yeolar   2014-12-14 22:46  

GFlags是Google开源的一个命令行flag(区别于参数)库。和 getopt() 之类的库不同,flag的定义可以散布在各个源码中,而不用放在一起。一个源码文件可以定义一些它自己的flag,链接了该文件的应用都能使用这些flag。这样就能非常方便地复用代码。如果不同的文件定义了相同的flag,链接时会报错。

GFlags是一个C++库,同时也有一个Python移植,使用完全相同的接口。

使用CMake链接GFlags

最新版本的GFlags已经可以支持CMake了。安装GFlags之后,可以在CMake中以下面方式使用:

1 find_package (gflags REQUIRED)
2 include_directories (${gflags_INCLUDE_DIR})
3 
4 add_executable (foo main.cc)
5 target_link_libraries (foo gflags)

如果你和我一样是从包管理器安装的,可以准备一个 FindGFlags.cmake 放到CMake的模块路径中,然后类似地使用(注意大小写有些区别,下面的更符合CMake的命名习惯):

1 find_package (GFlags REQUIRED)
2 include_directories (${GFLAGS_INCLUDE_DIR})
3 
4 add_executable (foo main.cc)
5 target_link_libraries (foo gflags)

DEFINE: 在程序中定义flag

定义flag只需使用你想要的类型的对应的宏即可,这些宏定义在 gflags/gflags.h 的最后。比如:

1 // foo.cc
2 #include <gflags/gflags.h>
3 
4 DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
5 DEFINE_string(languages, "english,french,german",
6               "comma-separated list of languages to offer in the 'lang' menu");

支持的类型有:

  • 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

没有列表之类的复杂类型,因此例子中的“languages”flag定义为string类型,而不是string列表之类的。这样保证了库的设计的简单。

DEFINE 宏有三个参数:flag名、默认值、描述使用方法的帮助。帮助会在执行 --help flag时显示。

可以在任何源文件中定义flag,但是每个只能定义一次。如果需要在多处使用,那么在一个文件中 DEFINE ,在其他文件中 DECLARE 。比较好的方法是在 .cc 文件中 DEFINE ,在 .h 文件中 DECLARE ,这样包含头文件即可使用flag了。

在库中定义flag很好用,但是也有些问题。比如一个库可能没有flag的合适的默认值。解决办法是可以使用flag验证器在没有有效flag值的时候给出提示。

注意: DEFINE_fooDECLARE_foo 是全局命名空间的。

使用flag

定义的flag可以像正常的变量一样使用,只需在前面加上 FLAGS_ 前缀。如前面例子中定义了 FLAGS_big_menuFLAGS_languages 两个变量。可以像其他变量一样读写:

1 if (FLAGS_consider_made_up_languages)
2   FLAGS_languages += ",klingon";   // implied by --consider_made_up_languages
3 if (FLAGS_languages.find("finnish") != string::npos)
4   HandleFinnish();

也可以使用 gflags.h 中的特殊函数读写flag,不过不太常用。

DECLARE: 在不同文件中使用flag

上面的方法只能在同一文件中前面定义了flag的情况下使用flag,否则会报“unknown variable”错误。

在不同文件中使用flag可以通过 DECLARE_type 宏来做到。比如,如果想在 bar.cc 文件中使用 big_menu flag,可以在文件开始加上:

1 DECLARE_bool(big_menu);

这和 extern FLAGS_big_menu 是等效的。

问题是这会在两个文件间加上依赖关系,对于较大的项目这会导致管理困难。所以这里有个原则:如果在 foo.cc 中 DEFINE 了一个flag,那么或者不 DECLARE 它,或者只在对应的测试中 DECLARE ,或者只在 foo.h 中 DECLARE

RegisterFlagValidator: 验证flag值

你可能想给定义的flag注册一个验证函数。这样当flag从命令行解析,或者值被修改(通过调用 SetCommandLineOption() ),验证函数都会被调用。验证函数应该在flag值有效时返回true,否则返回false。如果对新设置的值返回false,flag保持当前值;如果对默认值返回false, ParseCommandLineFlags 会失败。

举个例子:

1 static bool ValidatePort(const char* flagname, int32 value) {
2    if (value > 0 && value < 32768)   // value is ok
3      return true;
4    printf("Invalid value for --%s: %d\n", flagname, (int)value);
5    return false;
6 }
7 DEFINE_int32(port, 0, "What port to listen on");
8 static const bool port_dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort);

在全局初始化时注册( DEFINE 之后),这样就在解析命令行之前执行。

注册成功 RegisterFlagValidator() 返回true。否则返回false:a) 第一个参数不是命令行flag,b) 已经注册了另一个验证器。

生成flag

最后,还需要解析命令行。和 getopt() 类似,但是简单得多:

1 google::ParseCommandLineFlags(&argc, &argv, true);

通常把它放在 main() 的开始处,传入的 argcargv 参数可能被修改。

最后一个参数如果为true, ParseCommandLineFlags 会从 argv 删除flag,修改 argc ,最后只剩下命令行参数。

相反如果为false, argc 不会修改, argv 会被重新排列,flag在前,参数在后, ParseCommandLineFlags 会返回 argv 中第一个命令行参数的位置,即最后一个flag的后一个位置。

根据命令行的解析,修改 FLAGS_* 变量。

设置命令行flag

一般使用flag的原因是为了能在命令行指定一个非默认值。以 foo.cc 为例,可能的用法是:

app_containing_foo --nobig_menu -languages="chinese,japanese,korean" ...

执行 ParseCommandLineFlags 会设置 FLAGS_big_menu = falseFLAGS_languages = "chinese,japanese,korean"

注意这种在名字前面加“no”的设置布尔flag为false的语法。

设置“languages”flag的方法有:

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"

布尔flag稍有不同:

app_containing_foo --big_menu
app_containing_foo --nobig_menu
app_containing_foo --big_menu=true
app_containing_foo --big_menu=false

还包括以上这些的单短线的变种

建议只使用一种形式:非布尔flag, --variable=value ;布尔flag, --variable/--novariable 。保持一致性有一定的好处。

在命令行使用未定义的flag会在执行时失败。如果需要允许未定义的flag,可以使用 --undefok 来去掉报错。

getopt() 一样, -- 可以用于结束flag。

重复指定flag使用最后的一个。

不支持单字母的形式的flag,也不支持单短线后的flag合并,像 ls -la 那样。

更改默认的flag值

对于定义在库中的flag,有时我们想要在单独一个应用中改变它的默认值。很简单,只要在 ParseCommandLineFlags() 前面设定一个新的值即可:

1 DECLARE_bool(lib_verbose);   // mylib has a lib_verbose flag, default is false
2 int main(int argc, char** argv) {
3   FLAGS_lib_verbose = true;  // in my app, I want a verbose lib by default
4   ParseCommandLineFlags(...);
5 }

对于上面的应用中,flag的默认值被改为true。

特殊flag

GFlags中默认定义了一些flag。有三类,第一类是“报告”flag,用于打印一些信息然后退出。

--help 显示所有文件的所有flag,按文件、名称排序,显示flag名、默认值和帮助
--helpfull 和 --help 相同,显示全部flag
--helpshort 只显示执行文件中包含的flag,通常是 main() 所在文件
--helpxml 类似 --help,但输出为xml
--helpon=FILE 只显示定义在 FILE.* 中得flag
--helpmatch=S 只显示定义在 *S*.* 中的flag
--helppackage 显示和 main() 在相同目录的文件中的flag
--version 打印执行文件的版本信息

第二类是可以影响其他flag的。

--undefok=flagname,flagname,... --undefok 后面列出的flag名,可以在无定义的情况下忽略而不报错

第三类是“递归”flag,可以用来设置其他flag: --fromenv, --tryfromenv, --flagfile

--fromenv

--fromenv=foo,bar 表示从环境变量中读取 foobar flag。需要在环境中预先设置对应的值:

1 export FLAGS_foo=xxx; export FLAGS_bar=yyy   # sh
2 setenv FLAGS_foo xxx; setenv FLAGS_bar yyy   # tcsh

等价于在命令行指定 --foo=xxx --bar=yyy

如果在应用中没有定义 foo ,或者环境变量中没有定义 FLAGS_foo ,使用 --fromenv=foo 会导致失败。

--tryfromenv

--tryfromenv--fromenv 类似,区别是在环境变量中没有定义 FLAGS_foo 时, --tryfromenv=foo 不会导致失败,这时会使用定义时指定的默认值。但是应用中没有定义 foo 仍会导致失败。

--flagfile

--flagfile=f 表示从文件 f 中读取flag。

对于简单形式,文件 f 中每行一个flag。在flagfile文件中flag需要使用等号。如:

# /tmp/myflags
--nobig_menus
--languages=english,french

以下两种方式是等价的:

./myapp --foo --nobig_menus --languages=english,french --bar
./myapp --foo --flagfile=/tmp/myflags --bar

注意在flagfile中很多类型的错误会被忽略掉,比如不能识别的flag,没有指定值的flag。

一般形式的flagfile要复杂一些。写成一组文件名,每行一个,后面加上一组flag,每行一个的形式,可以有多组。文件名可以使用通配符( *? ),只有当前可执行模块名和其中一个文件名匹配时才会处理文件名后的flag。flagfile可以直接以一组flag开始,这时这些flag对应到当前可执行模块。

# 开头的行作为注释被忽略,前导空白和空行也都会被忽略。

flagfile中还可以使用 --flagfile flag来包含另一个flagfile。

flag会按顺序执行。从命令行开始,遇到flagfile时,执行文件,执行完再继续命令行中后面的flag。

其他一些细节

除以上的方法,还可以直接通过API来读取flag,以及它的默认值和帮助等信息。 FlagSaver 可以用来修改flag和自动撤销修改。还有一些读取 argv 的方法, SetUsageMessage()SetVersionString 等等。可以参考 gflags.h。

如果加上:

1 #define STRIP_FLAG_HELP 1    // this must go before the #include!
2 #include <gflags/gflags.h>

可以去掉帮助信息。

http://www.yeolar.com/note/2014/12/14/gflags/

http://www.yeolar.com/note/2014/12/14/gflags/