本文最初发布于2020年11月5日的TotW#186
作者:James Dennett和Jason Rennie
更新日期:2020-11-05
快速链接:abseil.io/tips/186
“一切都应该尽可能简单,但不简单。” ~ Roger Sessions对Einstein的解释
当添加新函数时,默认将其作为非成员函数添加到调用它的.cc文件中。虽然有其他有效的选择,但请考虑将其编写为未命名的命名空间(也称为“匿名命名空间”)。
好处
在未命名的命名空间中编写非成员函数既可以使函数内部化到.cc文件中(将其移出头文件),也可以使其成为非成员函数(将其移出类)。
与在头文件中声明的函数相比,未命名命名空间中声明函数的优点包括:
- 使读者可以轻松查找定义(因为它在使用相同文件中,且在第一次使用之上)。
- 在单个位置放置文档、声明和定义(相对于在头文件中声明函数需要两个位置)。
- 将其与其他源文件隔离,使重构更容易。
- 无需担心有意义的const,因为没有单独的声明。
- 将它们放在库的其他实现帮助程序(例如方便别名和本地类型)的相同位置。请参见本周提示#119:Using-declarations和Namespace Aliases。
- 提供副作用,例如允许类型也移动到未命名的命名空间中(如果仅在单个源文件中引用)。
相对于私有方法的优点包括:
输入和输出是清晰的,因为它们(更有可能)是通过参数或返回值指定的。请注意,方法可以读取任何成员变量,而非const方法可以修改任何非const成员。相反,非成员函数只能按照其接口读取或修改(除全局变量外)。
类API更简单、更短,因此更易于阅读——不必要的私有方法可能会使查找与继承相关的私有声明或类后声明变得困难。
寻找其他原因
有时,非成员局部函数是不合适的。例如:
- 当该函数在多个源文件中有用时。在头文件中声明它允许重用。
- 当该函数与对象或类有复杂的交互作用时。例如,从多个字段读取并需要以不能通过返回值自然处理的方式修改状态的函数可能最好编写为方法。特别是涉及互斥体的逻辑通常属于成员函数。
- 当该函数属于类的API的一部分时。
另一种选择:静态非成员函数
将非成员函数标记为静态函数在隔离它与其他翻译单元中的代码方面与将其放置在未命名命名空间中具有基本相同的效果。虽然未命名命名空间以统一的方式覆盖类型以及函数和对象,但有些人喜欢在函数声明中明确写出static,以显示它是本地翻译单元而无需检查是否有封闭未命名命名空间。虽然本周提示建议使用未命名命名空间,但使用static也可以是合理的选择。
其他参考资料
样式指南的某些部分指导我们朝这个方向发展,但它们没有走得太远。例如:
- 未命名命名空间部分鼓励定义放在未命名命名空间中(或声明为静态),但不谈论私有方法。
- 输入和输出部分鼓励使用返回值,但不涉及成员修改(通过this);这基本上是一个超级输入/输出参数。
- 局部变量部分鼓励最小化变量范围,但不将这个想法扩展到类和对象。
- 非成员、静态成员和全局函数部分不鼓励仅使用类来分组函数。
总结
文件局部函数简化了依赖关系并改善了局部性。非成员函数增加了封装性,简化了类定义,并使依赖关系更加明确。编写函数时,请考虑将其作为文件局部非成员函数,例如将其放在.cc文件的未命名命名空间中。