每周技巧 #103:标志就是全局变量

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #103: Flags Are Globals

作者:Matt Armstrong

.cc 文件的全局作用域定义 flag。最多在对应的 .h 文件中声明一次。

为什么要在头文件中声明东西?

对我们大多数人来说,使用头文件是一种本能,所以我们可能已经忘了为什么使用它们:

  1. 在头文件中声明某个东西,方便在别处 #include。整个程序会看到同一个声明。
  2. 在定义同一实体的 .cc 中包含这个头文件,可以确保定义与声明匹配。
  3. 头文件充当包 public API 的文档。使用包的 public API 之外的任何东西都不是好风格。
  4. 包含头文件,而不是重新声明实体,有助于工具和人类进行依赖分析。

Abseil Flags 和其他全局变量一样脆弱

你可以错误地这样做,而且不会产生链接期错误。首先,在一个 .cc 文件中放入:

1
2
// 在 .cc 文件中定义 --my_flag。
ABSL_FLAG(std::string, my_flag, "", "My flag is a string.");

然后,在另一个 .cc 文件(也许是测试)中放入下面这个错误的 flag 声明:

1
2
// 错误声明:类型应该是 std::string。
extern absl::Flag<int64> FLAGS_my_flag;

这个程序是病式的,任何发生的事情都是未定义行为的结果。在我的测试程序中,这段代码可以编译、链接,但在访问 flag 时崩溃。

建议

像设计全局变量一样设计命令行 flag。

  1. 如果可以,避免使用 flag。见 技巧 #45
  2. 如果你使用 flag 是为了让测试更容易写,而且完全没有在生产中设置它的意图,请考虑给你的类添加仅测试 API。
  3. 考虑把 flag 当作 private static 变量处理。如果其他包需要访问它们,请用函数包装。
  4. 在全局作用域定义 flag,而不是在命名空间中定义,这样当 flag 名称冲突时可以得到链接错误。
  5. 如果一个 flag 在多个文件中访问,请在一个与其定义对应的 .h 文件中声明它。
  6. 使用 ABSL_FLAG(type, ...) 宏定义 flag。

结论

flag 是全局变量。请谨慎使用它们。像使用和声明任何其他全局变量一样使用和声明它们。

每周技巧 #101:返回值、引用和生命周期

上一节

每周技巧 #107:引用生命周期延长

下一节