当前位置: 首页 > 工具软件 > Google GFlags > 使用案例 >

google gflags使用指南

乐健
2023-12-01

文章参考自google gflags官方github

介绍

命令行标志是用户在运行可执行文件时在命令行上指定的标志。在命令

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

-l-f /var/tmp/foo是两个命令行标志。(johannesbrahms不以破折号开头,是命令行参数。)

通常,应用程序会列出允许用户传入的标志以及它们采用的参数——在本例中, -l代表不采用参数,-f采用字符串(特别是文件名)作为参数。用户可以使用库来帮助解析命令行并将标志存储在某些数据结构中。

Gflags 是 Google 中使用的命令行标志库,它与其他库(例如getopt() )的不同之处在于标志定义可以分散在源代码中,而不仅仅是列在一个地方,例如main()。实际上,这意味着单个源代码文件将定义和使用对该文件有意义的标志。任何链接到该文件的应用程序都将获得标志,gflags 库将自动适当地处理该标志。

由于这种技术,灵活性和代码重用的简易性有了显著的提高。但是,存在两个文件定义相同标志的危险,然后当它们链接在一起时会给出错误信息。

下载和安装

gflags 库可以从 GitHub 下载。 您可以使用以下命令克隆项目:

git clone https://github.com/gflags/gflags.git

INSTALL 文件中提供了构建和安装说明。

CMake依赖声明

这里只说明了如何使用已安装好的gflags库,对于将gflags源代码包含到项目中的操作请参考官方文档

find_package(gflags REQUIRED)
add_executable(foo main.cc)
target_link_libraries(foo gflags::gflags)

DEFINE:在程序中定义标志

定义一个标志很容易:只需为您希望标志的类型使用适当的宏,如 gflags/gflags.h 底部定义的那样。这是一个示例文件 foo.cc

   #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_bool 定义了一个布尔标志。以下是支持的类型:

 DEFINE_bool	:布尔值
 DEFINE_int32	:32位整数
 DEFINE_int64	:64位整数
 DEFINE_uint64	:64位无符号整数
 DEFINE_double	:双精度浮点数
 DEFINE_string	:C++ 字符串

请注意,没有像列表这样的“复杂”类型:我们示例中的“语言”标志是字符串列表,但定义为“字符串”类型,而不是“list_of_string”或类似类型。这是设计使然。我们宁愿只对标志使用简单的类型,并允许复杂的、任意的解析例程来解析它们,而不是尝试将解析逻辑放入标志库中。

所有 DEFINE 宏都采用相同的三个参数:标志的名称、其默认值和描述其用途的“help”字符串。当用户使用 --help 标志运行应用程序时,会显示“help”字符串。

您可以在可执行文件的任何源代码文件中定义标志。只定义一次标志!如果要在多个源文件中访问一个标志,请在一个文件中定义它,并在其他文件中声明它。更好的做法是,在 foo.cc 中定义它并在 foo.h中声明它;那么#includes foo.h的每个人都可以使用该标志。

在库中而不是在 main() 中定义标志是强大的,但确实需要付出一些额外成本。一个是库的标志可能没有一个好的默认值,例如,如果标志包含在某些环境中可能不存在的文件名。为了缓解此类问题,您可以使用flag validators来确保对无效标志值进行及时通知(以崩溃的形式)。

注意,虽然此库中的大多数函数是在 google 命名空间中定义的,但 DEFINE_foo(和下面的 DECLARE_foo)应始终位于全局命名空间中。

查询Flags

所有定义的标志都可以作为普通变量用于程序,并带有前缀FLAGS_ 。 在上面的例子中,宏定义了两个变量,FLAGS_big_menu(一个布尔值)和FLAGS_languages(一个 C++ 字符串)。

您可以像任何其他变量一样读取和写入标志:

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

您还可以通过gflags.h 中的特殊函数获取和设置标志值。 不过,这是一个罕见的用例。

声明:在不同的文件中使用标志

以上一节的方式访问标志仅适用于在文件顶部定义标志的情况。如果不是,您将收到“未知变量”错误。

当您想要使用在另一个文件中定义的标志时,可以使用 DECLARE_type宏。例如,如果我正在编写bar.cc 但想要访问big_menu标志,我会将其放在 bar.cc 顶部附近:

DECLARE_bool(big_menu);

这在功能上等同于说 extern FLAGS_big_menu

请注意,这样的 extern 声明在您的文件和定义big_menu标志的文件之间引入了依赖关系:在本例中为 foo.cc。在大型项目中,这种隐式依赖可能难以管理。因此,我们建议遵循以下准则:

如果你在foo.cc中定义了一个标志,要么根本不DECLARE它,只在紧密相关的测试用例中声明它,或者只在foo.h中声明它。

当标志只被 foo.cc需要时,你应该走do-not-DECLARE 路线,而不是在任何其他文件中。如果要修改相关测试文件中标志的值以查看其是否按预期运行,请在 foo_test.cc文件中声明它。

如果该标志确实跨越多个文件,请在关联的.h文件中声明它,如果其他人想要访问该标志,则让其他人#include.h 文件。#include 将明确两个文件之间的依赖关系。这会导致标志成为全局变量。

RegisterFlagValidator: Sanity-checking Flag Values

在定义标志后,您可以选择为这个标志注册验证器函数。如果这样做,在从命令行解析标志后,每当通过调用SetCommandLineOption()更改其值时,都会使用新值作为参数调用验证器函数。如果标志值有效,验证器函数应返回“true”,否则返回"false"。如果函数为新设置的标志值返回 false,则标志将保留其当前值。如果它为默认值返回 false,ParseCommandLineFlags 将死亡。

以下是此功能的使用示例:

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");
DEFINE_validator(port, &ValidatePort);

通过在全局初始化时(在DEFINE_int32 之后)进行注册,我们确保在main()开头解析命令行之前进行注册。

上面使用的DEFINE_validator宏调用RegisterFlagValidator(),如果注册成功他返回"true"。如果注册失败,则返回 “false”,因为
a) 第一个参数不是一个命令行标志,
b) 已为该标志注册了不同的验证器。
返回值可用作名为<flag>_validator_registered 的全局静态布尔变量。

如何设置标志

最后一部分是告诉可执行文件处理命令行标志,并根据在命令行上看到的内容将FLAGS_* 变量设置为适当的非默认值。这等效于 getopt 库中的getopt() 调用,但使用的开销要少得多。实际上,这只是一个函数调用:

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

通常,此代码位于main()的开头。argcargv 与传递给 main()的完全一样。此程序可能会修改它们,这就是传入它们的指针的原因。

最后一个参数称为“remove_flags”。如果为 true,则 ParseCommandLineFlags 从argv中删除标志及其参数,并适当修改 argc。在这种情况下,在函数调用之后,argv 将只保存命令行参数,而不保存命令行标志。

另一方面,如果 remove_flagsfalse,则 ParseCommandLineFlags 将保持argc 不变,但会重新排列argv 中的参数,以便所有标志都在开头。例如,如果输入是“/bin/foo”“arg1”“-q”“arg2”(这是合法但很奇怪),该函数将重新排列 argv 使其读取“/bin/foo”、“-q” 、“arg1”、“arg2”。在这种情况下,ParseCommandLineFlags 将索引返回到保存第一个命令行参数索引的argv 中:即,最后一个标志之后的索引。 (在本例中,它将返回 2,因为 argv[2] 指向 arg1。)

在任何一种情况下, FLAGS_*变量都会根据命令行中传入的内容进行修改。

在命令行中设置标志

您将某些东西作为标志而不是编译期常量的原因是,用户可以在命令行上指定非默认值。以下是他们如何为在foo.cc 中链接的应用程序执行此操作:

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

这将设置FLAGS_big_menu = false;和FLAGS_languages = "chinese,japanese,korean",当 ParseCommandLineFlags 运行时。

请注意将布尔标志设置为 false 的非典型语法:在其名称前放置“no”。如何指定标志有相当大的灵活性。这是指定“languages”标志的所有方法的示例:

    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" 

对于布尔标志,可能性略有不同:

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

(以及所有这些的单破折号变体)。

尽管有这种灵活性,我们建议只使用一种形式:--variable=value 用于非布尔标志,--variable/--novariable 用于布尔标志。这种一致性将使您的代码更具可读性,并且也是某些特殊用例(如标志文件)所需的格式。

在命令行上指定尚未在可执行文件中某处定义的标志是一个致命错误。如果您出于某种原因需要该功能 - 假设您想为多个可执行文件使用相同的标志集,但不是所有可执行文件都定义了列表中的每个标志 - 您可以指定--undefok 来抑制错误。

getopt() 一样, --本身将终止标志处理。所以在foo -f1 1 -- -f2 2 中,f1被认为是一个标志,但-f2 不是。

如果一个标志被指定多次,则只使用最后一个指定;其他则被忽略。

请注意,我们不允许在单个破折号后面“组合”标志,如 ls -la

更改默认标志值

有时在库中定义了一个标志,而您想在一个应用程序中更改其默认值而不改变其他程序。 这样做很简单:在调用 ParseCommandLineFlags() 之前,只需为main() 中的标志分配一个新值:

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

对于这个应用程序,用户仍然可以在命令行上设置标志值,但如果不这样做,标志的值将默认为 true。

特殊标志

commandlineflags 模块本身定义了一些标志,可用于所有使用 commandlineflags 的应用程序。具体可以参考特殊标志用法

 类似资料: