每周技巧 #186:优先把函数放进未命名命名空间
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #186: Prefer to Put Functions in the Unnamed Namespace。
原文最初作为 TotW #186 发布于 2020 年 11 月 5 日。
作者:James Dennett 和 Jason 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 也是合理选择。
其他参考
风格指南的部分内容把我们引向这个方向,但没有走得这么远。例如:
- 未命名命名空间一节鼓励把定义放进未命名命名空间(或声明为
static),但没有谈私有方法。 - 输入和输出一节鼓励使用返回值,但没有讨论成员修改(通过
this);this本质上是一个超级输入/输出参数。 - 局部变量一节鼓励最小化变量作用域,但没有把这个想法扩展到类和对象。
- 非成员、静态成员和全局函数一节不鼓励仅仅为了分组函数而使用类。
总结
文件局部函数可以简化依赖并改善局部性。非成员函数提高封装性、简化类定义,并让依赖更显式。编写函数时,请考虑把它做成文件局部非成员函数,例如放进 .cc 文件的未命名命名空间中。