每周技巧 #186:优先把函数放进未命名命名空间

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #186: Prefer to Put Functions in the Unnamed Namespace

原文最初作为 TotW #186 发布于 2020 年 11 月 5 日。

作者:James DennettJason Rennie

更新于 2020 年 11 月 5 日。

快捷链接:abseil.io/tips/186

“一切都应尽可能简单,但不能更简单。” ~ Roger Sessions 对爱因斯坦观点的诠释

添加新函数时,默认做法应是让它成为调用它的 .cc 文件中的局部非成员函数。虽然选择其他做法也可能有正当理由,但请考虑把它写在未命名命名空间中(也称为“匿名命名空间”)。

好处

把非成员函数写在未命名命名空间中,有两类好处:让函数成为 .cc 文件内部实现(把它们移出头文件),以及让它们成为非成员(把它们移出类)。

相比在头文件中声明的函数,好处包括:

  • 读者很容易查找定义(因为定义和使用在同一文件中,并且位于第一次使用之前)。
  • 文档、声明和定义放在同一个位置(而头文件声明的函数通常分散在两个位置)。
  • 与其他源文件隔离,更容易重构。
  • 因为没有单独声明,不需要担心有意义的 const
  • 让它们和库的其他实现辅助内容放在同一处,例如便利别名和局部类型。见每周技巧 #119:using 声明和命名空间别名
  • 提供附带好处,例如如果某个类型只在单个源文件中引用,也可以把它移到未命名命名空间中。

相比私有方法,好处包括:

  • 输入和输出更清楚,因为它们(更有可能)通过实参或返回值指定。注意,一个方法可以读取任何成员变量,非 const 方法可以修改任何非 const 成员。相反,非成员函数只能按其接口读取或修改内容(全局变量除外)。
  • 类 API 更简单、更短,因此更容易阅读。不必要的私有方法可能让人更难找到与继承相关的私有声明,或者类声明后面的内容。

即使没有相关头文件,例如在 *_test.cc*_main.cc 文件中,这些好处大多仍然成立。

需要另寻位置的理由

有时,局部非成员函数并不合适。例如:

  • 函数在多个源文件中有用。把它声明在头文件中可以复用。
  • 函数与某个对象或类有复杂交互。例如,一个函数读取多个字段,并且需要以无法自然通过返回值处理的方式修改状态,通常更适合写成方法。特别是,涉及 mutex 的逻辑通常属于成员函数。
  • 函数本就属于某个类 API 的一部分。

替代方案:static 非成员函数

把非成员函数标记为 static,在将其与其他翻译单元中的代码隔离方面,与放进未命名命名空间基本效果相同。未命名命名空间以统一方式做到这一点,覆盖类型、函数和对象;但有些人喜欢在函数声明中显式看到 static,以表示它是翻译单元局部的,而不必检查外围是否有未命名命名空间。虽然本技巧建议使用未命名命名空间,但使用 static 也是合理选择。

其他参考

风格指南的部分内容把我们引向这个方向,但没有走得这么远。例如:

总结

文件局部函数可以简化依赖并改善局部性。非成员函数提高封装性、简化类定义,并让依赖更显式。编写函数时,请考虑把它做成文件局部非成员函数,例如放进 .cc 文件的未命名命名空间中。

每周技巧 #182:初始化你的整数!

上一节

每周技巧 #187:`std::unique_ptr` 必须被移动

下一节