gflags的使用实例(转载)

参考:http://blog.csdn.net/lezardfu/article/details/23753741

安装gflag
从官方地址http://code.google.com/p/google-gflags/下载gflags并安装。比如我下载的是1.5版本。
[yichi@yichi tmp]$ tar zxvf gflags-1.5.tar.gz
[yichi@yichi tmp]$ cd gflags
[yichi@yichi gflags-1.5]$ ./configure –prefix=$HOME/google-library
[yichi@yichi gflags-1.5]$ make j=4 & make install

google gflags是google使用的一个开源库,用于解析命令行标记。目前的gflags有C++和Python两个版本。本文主要介绍gflags的C++版本使用方法。

命令行标记是指用户在运行可执行程序时,在命令行中指定的参数。例如,对于命令:

fgrep -l -f /var/tmp/foo johannes brahms

则-l及-f /var/tmp/foo是两个命令行标记,而johnnes和brahms则是命令行参数。一个应用往往具有许多个命令行标记,对应这些标记,可以传入一些由用户指定的参数。例如,在上面的例子中,-l标记没有任何参数,而-f标记的参数则是/var/tmp/foo。

这些标记和参数存储在main函数的argc和argv参数中。一般而言,main函数总有如下的形式:

int main(int argc, char ** argv) {…}

其中argc指明了argv数组的长度,而argv数组则空格为分割,存储命令行后用户输入的参数。在GNU C Library中,这个工作主要是由getopt函数来完成的。以下是getopt函数的一个例子:

#include
#include
#include
#include
 
int
main (int argc, char **argv)
{
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;
 
opterr = 0;
 
while ((c = getopt (argc, argv, "abc:")) != -1)
switch (c)
{
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = optarg;
break;
case '?':
if (optopt == 'c')
fprintf (stderr, "Option -%c requires an argument.n", optopt);
else if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.n", optopt);
else
fprintf (stderr,
"Unknown option character `x%x'.n",
optopt);
return 1;
default:
abort ();
}
 
printf ("aflag = %d, bflag = %d, cvalue = %sn",
aflag, bflag, cvalue);
 
for (index = optind; index < argc; index++)
printf ("Non-option argument %sn", argv[index]);
return 0;
}

在这个例子中可以看出,所有的标记的定义都必须要集中在一个地方处理(也就是switch代码段中)。而gflags则与getopt函数不同,在gflags中,标记的定义分散在源代码中,不需要列举在一个地方。这意味着单个的源代码文件可以定义他们自己所使用的标记,一切连接到这个文件的应用将获取这个标记,并且gflags将自动对这个标记进行适当的处理。这对于软件的灵活性及代码的重用是很有好处的。当然,这也产生了一定的风险,特别是,两个不同的文件可能会定义拥有同一个名字的命令行标记,在平常的情况下没有问题,但当这两个文件被同一个二进制程序连接的时候,就会出现错误。

gflags使用C++语言实现,其风格也是C++风格的。本文的其余部分介绍了如何使用gflags。

一、如何定义一个命令行标记

使用gflags需要引用gflags头文件:

#include <gflags/gflags.h>

在gflags.h文件的结尾,定义了一些标记定义宏,因此在引用了gflags.h后,定义一个命令行标记就变得十分简单了。如,在foo.cc文件的开头,我们引入如下的命令行标记的定义:


#include
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型的big_menu,以及string型的language。所有的DEFINE宏都有三个参数:标记的名字,默认值,以及针对这个标记的说明。第三个参数是非常有用的,尤其是在构建大型程序的时候,在命令行中使用—help标记可以列出被gflags所定义的所有标记的类型、默认值及对其的说明(也就是宏第三个参数)。

gflags主要支持的参数类型包括bool,int32, int64, uint64, double, string等,定义参数通过DEFINE_type宏实现,如下所示,分别定义了一个bool和一个string类型的参数,该宏的三个参数含义分别为命令行参数名,参数默认值,以及参数的帮助信息。

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"); 

gflag不支持列表,用户通过灵活借助string参数实现,比如上述的languages参数,可以类型为string,但可看作是以逗号分割的参数列表。

DEFINE宏支持的类型包括:

o DEFINE_bool: boolean

o DEFINE_int32: 32-bit integer

o DEFINE_int64: 64-bit integer

o DEFINE_uint64: unsigned 64-bit integer

o DEFINE_double: double

o DEFINE_string: C++ string

可以在可执行程序的任何一个源文件中定义一个标记,而针对每个标记,只需要定义一次。如果想要在不同的源文件中访问该标记,可以使用DECLARE宏来声明这个标记(其实就是声明了一个extern变量的引用)。一个更好的方法是,对于一个标记,可以在foo.cc中定义它,并且在foo.h中声明它。这样,包含了foo.h的所有源文件都可以引用这个标记。

二、如何访问一个标记

访问标记较为简单。只要对标记进行了定义或者声明,那么只需要在使用它的地方加上前缀“FLAGS_”就可以引用这个标记了。如,

if (FLAGS_consider_made_up_languages)
FLAGS_languages += ",klingon"; // implied by --consider_made_up_language
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");

三、在命令行中使用标记

对于上面的foo.cc和foo.h,如果其编译后产生的二进制文件为app_containing_foo,则在命令行中设置这些标记的方法为:

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

这个命令会把big_menu设置为false,并且将language设置为“chinese,japanese,Korean”。对于string、int、double等类型的标记,设置标记时使用如下形式:

 

o app_containing_foo --languages="chinese,japanese,korean"

o app_containing_foo -languages="chinese,japanese,korean"

o app_containing_foo --languages "chinese,japanese,korean"

o app_containing_foo -languages "chinese,japanese,korean"

 这些方式是等价的。但注意gflags不支持短标记与合并标记。如ls –la这种类型,是不能够被gflags处理的。这和getopt函数有所不同。

需要注意的还有以下几点:

1,--连用会终止标记。如命令:foo -f1 1 -- -f2 2,则f2标记将不被处理;

2,bool型的标记,如big_menu,既可以使用—big_menu=true/false,也可以使用—big_menu/--nobig_menu这种方式。gflags都可以解析。

3,使用未被定义的标记将引发严重错误。

编译成可执行文件之后,用户可以使用:executable --参数1=值1 --参数2=值2 ... 来为这些命令行参数赋值。

./mycmd --var1="test" --var2=3.141592654 --var3=32767 --mybool1=true --mybool2 --nomybool3

这里值得注意的是bool类型命令行参数,除了可以使用--xxx=true/false之外,还可以使用--xxx和--noxxx后面不加等号的方式指定true和false

四、在程序中解析标记

在程序中解析标记可以使用如下函数:

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

注意argc和argv均以地址方式传递,也就是该函数可以改变argc和argv的内容。通常这段代码会出现在main函数的开始。最后一个参数是移除标记,它指明了是否将被gflags成功解析的标记从参数列表中移除。例如,对于命令行:

foo –options=start file1 file2

在这个命令行中,options是一个命令行标记,将被gflags解析(如果它已经被定义了);而file1和file2则是命令行参数。如果设置remove标记位ture,则—options=start会在调用ParseCommandLineFlags后被移除,此时argc=2,而argv中则保存了file1和file2两个参数。如果remove标记位设为false,则—options=start将不会被移除,则argc不会改变,但argv会重新排列,将所有的标记排列在前面。

argc和argv想必大家都很清楚了,说明以下第三个参数的作用:
如果设为true,则该函数处理完成后,argv中只保留argv[0],argc会被设置为1。
如果为false,则argv和argc会被保留,但是注意函数会调整argv中的顺序。

参数检查 
定义参数后,可以给参数注册一个检查函数(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
", 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",这样更好理解。



gflags进阶使用:
    1. 在其他文件中使用定义的flags变量:有些时候需要在main之外的文件使用定义的flags变量,这时候可以使用宏定义DECLARE_xxx(变量名)声明一下(就和c++中全局变量的使用是一样的,extern一下一样)
      • DECLARE_bool: boolean
      • DECLARE_int32: 32-bit integer
      • DECLARE_int64: 64-bit integer
      • DECLARE_uint64: unsigned 64-bit integer
      • DECLARE_double: double
      • DECLARE_string: C++ string
        在gflags的doc中,推荐在对应的.h文件中进行DECLARE_xxx声明,需要使用的文件直接include就行了。
        
    2. 检验输入参数是否合法:gflags库支持定制自己的输入参数检查的函数,如下:
      static bool ValidatePort(const char* flagname, int32 value) {
         if (value > 0 && value < 32768)   // value is ok
           return true;
         printf("Invalid value for --%s: %d
      ", flagname, (int)value);
         return false;
      }
      DEFINE_int32(port, 0, "What port to listen on");
      static const bool port_dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort);
    3. 判断flags变量是否被用户使用:在gflags.h中,还定义了一些平常用不到的函数和结构体。这里举一个例子,判断参数port有没有被用户设定过
          google::CommandLineFlagInfo info;
          if(GetCommandLineFlagInfo("port" ,&info) && info.is_default) {
              FLAGS_port = 27015;
          }
    4. 定制你自己的help信息与version信息:(gflags里面已经定义了-h和--version,你可以通过以下方式定制它们的内容)
      • version信息:使用google::SetVersionString设定,使用google::VersionString访问
      • help信息:使用google::SetUsageMessage设定,使用google::ProgramUsage访问
      • 注意:google::SetUsageMessage和google::SetVersionString必须在google::ParseCommandLineFlags之前执

特殊参数

  • --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来包含其他的文件。
原文地址:https://www.cnblogs.com/airlove/p/4689181.html