C++ 第三方参数库 gflags 介绍

1,211 阅读7分钟

个人主页:二郎腿 (erlangtui.top)

一、基本介绍

  • gflag 是 google 开源的处理命令行参数的库,用 C++ 编写的,同时提供了 Python 接口;
  • 其参数定义可以分散在各个文件中,而不是局限于某一个文件,复用性较强;
  • 相比于 getopt 应用起来更加灵活与方便,常用于各种开源库中;
  • github地址
  • 官方文档

二、安装与编译

1,安装二进制包

  • Debian/Ubuntu Linux 上,可以使用 apt-get 命令安装:
sudo apt-get install libgflags-dev
  • CentOS Linux 上,可以使用 yum 命令安装:
sudo yum install libgflags-dev
  • macOS 上,使用 Homebrew 安装:
brew install gflags

2,cmake 编译源文件

  • gflags 的编译要求 CMake 的版本不低于 3.0.2;
  • 可以从 github 仓库克隆源文件或是直接下载源文件压缩包;
git clone https://github.com/gflags/gflags
cd gflags
git checkout v2.2.2 (optional)
mkdir build
cd build
cmake ..
make
make test    (optional)
make install (optional)

三、使用方法

1,支持类型

类型描述
DEFINE_bool布尔
DEFINE_int3232 位整数
DEFINE_int6464 位整数
DEFINE_uint64无符号 64 位整数
DEFINE_double双精度浮点数
DEFINE_stringC++字符串

2,定义方式

  • 在某个文件中键入以下代码定义某些标志;
// 文件名为:com_flags.cpp
#include <gflags/gflags.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");
  • 所有 DEFINE 宏都采用相同的三个参数:标志的名称、其默认值和用法描述;
  • 可以在可执行文件中的任何源代码文件中定义标志,并在其他文件中声明它后就可以在该文件使用;

3,访问方式

  • 所有定义的标志,在程序中都可以想变量一样使用它;
  • 对应的变量名为:FLAGS_ 后加上定义的标志名;
  • 如上面的定义的标志名对应的变量名为:FLAGS_big_menu, FLAGS_languages

a) 同文件访问

  • 同文件直接通过变量名访问;
// 文件名为:com_flags.cpp

if (FLAGS_big_menu)
    FLAGS_languages += "gflags";
if (FLAGS_languages.find("flag") != string::npos)
    HandleFinnish();

b) 跨文件访问

  • 当需要在其他文件中使用该标志时,需要先声明再访问

  • 声明的方式就是将定义标志的 DEFINE 换成 DECLARE,相当于 C++ 的关键字 extern

  • 以上述例子为基础,在 main.cc 文件中访问 com_flags.cpp 文件中定义标志;

  • 需要先在 com_flags.cpp 的头文件 com_flags.h 中进行声明,然后在 main.cc 文件中包含该头文件既可:

    // 文件名:com_flags.h
    
    // 标志的声明
    DECLARE_bool(big_menu);
    DECLARE_string(languages;
    
    // 文件名:main.cc
    include "com_flags.h"
    
    if (FLAGS_big_menu)
      FLAGS_languages += "gflags";
    if (FLAGS_languages.find("flag") != string::npos)
      HandleFinnish();
    
  • 当变量的定义是在命名空间内定义时,声明的时候也需要带上命名空间

    // brpc 中的consul服务的标志定义
    namespace brpc {
    namespace policy {
    
    DEFINE_string(consul_agent_addr, "http://127.0.0.1:8500",
                  "The query string of request consul for discovering service.");
    DEFINE_string(consul_service_discovery_url,
                  "/v1/health/service/",
                  "The url of consul for discovering service.");
    }
    }
    
    // 文件名:main.cc
    
    // 在其他文件中声明标志时带上命名空间
    namespace brpc {
    namespace policy {
    
    DECLARE_string(consul_agent_addr,);
    DECLARE_string(consul_service_discovery_url);
    // ......
    }
    }
    

4,注册标志验证器

  • 为了校验参数是否符合自定义的要求,可以为定义标志注册一个验证器函数;
  • 每当该标志解析或修改时,都会调用验证器函数 使用新值作为参数;
  • 验证器函数应该返回一个布尔值,如果标志值有效,则返回 true,否则返回 false
  • 如果函数返回 false,则标志将保留其当前值;
  • 确保注册函数发生在命令行解析的开头;
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);
  • 如果注册成功,注册函数返回值为ture。否则返回false,注册失败一般是一下两种原因:
    • 第一个参数不引用命令行标志;
    • 已经为此标志注册了不同的验证器;

5,命令行标志

a) 解析

  • main 函数的开头使用 gflags::ParseCommandLineFlags(&argc, &argv, remove_flags) 函数可以帮助解析出相应的 flags
  • 最后一个参数 remove_flags
    • 若设置为 true,表示解析后将flag以及其对应的值从 argv 中删除,并相应的修改 argc,即最后存放的是不包含flag的参数(不清楚为啥这样做);
    • 若设置为 false,则仅对参数进行重排,标志位参数放在最前面;

b) 设置

  • 标志前可用单短线或双短线,标志后可用等号或空格;
  • 布尔类型标志前可以添加 no 标识 false,没有且后面没有值时表示 true
  • 命令行标志的设置方式较多,实际使用时建议使用其中一种,提高可读性;
./main --languages="chinese,japanese,korean"
./main -languages="chinese,japanese,korean"
./main --languages "chinese,japanese,korean"
./main -languages "chinese,japanese,korean"
./main --big_menu
./main --nobig_menu
./main --big_menu=true
./main --big_menu=false

6,特殊标志

a) 报告标志

标志作用
--help显示所有文件的所有标志,按文件排、名称排序,显示标志名称、其默认值及其帮助
--helpfull与 -help 相同,但明确要求所有标志(以防将来帮助发生变化)
--helpshort仅显示与可执行文件同名的文件的标志(通常是包含main())
--helpxml像 --help 一样,但输出是 XML 格式的,以便于解析
--helpon=FILE显示 FILE 文件中定义的标志
--helpmatch=S显示 S 中定义的标志
--helppackage显示和 main() 在相同目录的文件中的flag
--version打印可执行文件的版本信息

b) 功能标志

  • --undefok=flagname,...:这个标志在命令行无定义时不会报错退出,即使在程序中未使用 --undefok--namename
  • --fromenv=bar:从环境中读取 bar 标志的值,必须在环境变量定义该值,如 export FLAGS_bar=yyy,等效于在命令行使用 --bar=yyy,缺省时可以通过 --undefok=bar 忽略错误;
  • --tryfromenv=foo:应用程序保留其默认值,并尝试从环境变量中获取,未定义时不会报错;
  • --flagfile=file1:从文件读取命令行标志,可以接一系列文件名,并支持通配符,每个标志一行,可以用 # 注释;

7,定制版本与帮助

  • 通过 --version 会输出其对应的版本, SetVersionString() 设置版本信息;
  • 通过 --help 会输出帮助信息,SetUsageMessage() 设置帮助信息,与上面的 help 不冲突,会先输出此处设置帮助信息,再输出标志的信息;
  • 参数的设置需要在调用 ParseCommandLineFlags() 之前;
gflags::SetVersionString("1.1.0");
gflags::SetUsageMessage("./main --foo=yyy");
gflags::ParseCommandLineFlags(&argc, &argv, true);

四、使用示例

1,目录结构

.
├── CMakeLists.txt
├── README.md
├── build.sh 
├── gflags
│   ├── AUTHORS.txt
│   ├── BUILD
│   ├── CMakeLists.txt
│   ├── COPYING.txt
│   ├── ChangeLog.txt
│   ├── INSTALL.md
│   ├── README.md
│   ├── WORKSPACE
│   ├── appveyor.yml
│   ├── bazel
│   ├── cmake
│   ├── doc
│   ├── mybuild
│   ├── src
│   └── test
├── main.cc
└── mybuild
    ├── CMakeCache.txt
    ├── CMakeFiles
    ├── Makefile
    ├── cmake_install.cmake
    ├── gflags
    └── main

2,示例代码

#include <gflags/gflags.h>
#include <iostream>

// 标志验证器
static bool ValidatePort(const char *flagname, int32_t value)
{
  if (value > 0 && value < 32768) // value is ok
    return true;
  printf("Invalid value for --%s: %d\n", flagname, (int)value);
  return false;
}

// 标志定义
DEFINE_bool(big_menu, true, "Include 'advance' option in the menu listing");
DEFINE_string(languages, "english, french, chinese", "comma-separated list of languages to offer in the 'lang' menu");
DEFINE_int32(port, 9988, "What port to listen on");

// 注册标志验证器
static const bool port_dummy = gflags::RegisterFlagValidator(&FLAGS_port, &ValidatePort);

int main(int argc, char **argv)
{
  // 设置版本
  gflags::SetVersionString("1.1.0");
  // 设置使用帮助信息
  gflags::SetUsageMessage("./main --big_menu --languages=\"english, french, chinese\" --port=9988");
  // 命令行参数解析
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  std::cout << std::boolalpha; // 为了让bool类型输出true/false,而不是 1/0
  std::cout << "big_menu: " << FLAGS_big_menu << '\n'
            << "languages: " << FLAGS_languages << '\n'
            << "port: " << FLAGS_port << std::endl;

  return 0;
}

3,编译脚本

cmake_minimum_required(VERSION 2.8.12)
project(gflags_demo)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)

set(CMAKE_C_FLAGS       "${CMAKE_C_FLAGS} -g -w -O3 -DNDEBUG -std=c++11")
set(CMAKE_CXX_FLAGS     "${CMAKE_CXX_FLAGS} -g -w -O3 -DNDEBUG -std=c++11")
add_subdirectory(gflags)                   # 添加子目录

add_executable(main main.cc)               # 生成目标文件 main
target_link_libraries(main gflags::gflags) # 链接到 gflags库

4,执行脚本

#!/bin/bash

set -x -e

# git clone git@github.com:gflags/gflags.git 第一次执行时下载
rm -rf mybuild
mkdir mybuild
cd mybuild
cmake ..
make
./main
./main --version
./main --help
./main --port=99999

参考文章
[1] gflags.github.io/gflags/
[2] cloud.tencent.com/developer/a…
[3] izualzhy.cn/gflags-intr…
[4] gohalo.me/post/packag…
[5] zhuanlan.zhihu.com/p/369214077