每周技巧 #119:using 声明和命名空间别名
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #119: Using-declarations and namespace aliases。
原文最初作为 totw/119 发布于 2016 年 7 月 14 日。
作者:Thomas Köppe
本技巧给出一个简单、稳健的做法,用于在 .cc 文件中编写 using 声明和命名空间别名,并避免微妙陷阱。
进入细节之前,先看一个应用该做法的例子:
|
|
请记住,本技巧中的所有内容只适用于 .cc 文件,因为你绝不应该把便利别名放进头文件。这类别名是给实现者(以及实现代码读者)的便利,不是导出的设施。(当然,确实属于导出 API 的名字可以声明在头文件中。)
总结
- 绝不要在头文件的命名空间作用域声明命名空间别名或便利 using 声明,只在
.cc文件中这样做。 - 在最内层命名空间中声明命名空间别名和 using 声明,无论该命名空间是具名还是匿名。(不要只为了这个目的添加匿名命名空间。)
- 声明命名空间别名和 using 声明时,除非你引用的是当前命名空间内的名字,否则使用完全限定名(带前导
::)。 - 对名称的其他使用,在合理时避免完全限定,见 TotW 130。
(请记住,你总是可以在块作用域中拥有局部命名空间别名或 using 声明,这在 header-only 库中可能很方便。)
背景
C++ 用命名空间组织名称。这个关键设施让代码库能够扩展:通过把名称所有权保持在局部,避免其他作用域中的名称冲突。不过,命名空间会带来一定外观负担,因为限定名(foo::Bar)通常很长,很快会变得杂乱。我们常常觉得使用非限定名(Bar)更方便。此外,我们可能想为一个很长但频繁使用的命名空间引入命名空间别名:namespace eu = example::v1::util; 本技巧中,我们把 using 声明和命名空间别名统称为别名。
问题
命名空间的目的是帮助代码作者避免名称冲突,包括名称查找时的冲突和链接时的冲突。别名可能削弱命名空间提供的保护。这个问题有两个独立方面:别名的作用域,以及相对限定名的使用。
别名的作用域
你放置别名的作用域,可能对代码可维护性产生微妙影响。考虑下面两个变体:
|
|
看起来两个 using 声明都能让名称 Bar 和 Quz 在我们的工作命名空间 ::example::util 内通过非限定查找可用。对 Bar 来说,只要命名空间 ::example::util 内没有其他 Bar 声明,一切都按预期工作。但这是你的命名空间,所以你有能力控制这一点。
另一方面,如果之后包含了一个声明全局名称 Quz 的头文件,那么第一个 using 声明会变成病式,因为它试图重新声明名称 Quz。如果另一个头文件声明了 ::example::Quz 或 ::example::util::Quz,那么非限定查找会找到那个名称,而不是你的别名。
如果你不向自己不拥有的命名空间(包括全局命名空间)添加名称,就可以避免这种脆弱性。把别名放在你自己的命名空间里,非限定查找会首先找到你的别名,并且不会继续搜索包含命名空间。
更一般地说,声明离使用点越近,能破坏你代码的作用域集合就越小。在我们的例子中,最糟糕的是 Quz,任何人都可以破坏它;Bar 只能被 ::example::util 中的其他代码破坏;而在匿名命名空间中声明并使用的名字,不会被任何其他作用域破坏。示例见匿名命名空间。
相对限定名
形如 using foo::Bar 的 using 声明看起来无害,但实际上有歧义。问题在于,依赖某个命名空间中名称的存在是安全的,但依赖名称的不存在是不安全的。考虑这段代码:
|
|
作者的意图也许是使用名称 ::foo::Bar。然而这可能会坏掉,因为代码依赖 ::foo::Bar 的存在,同时也依赖命名空间 ::example::foo 和 ::example::util::foo 不存在。通过完全限定被使用的名称可以避免这种脆弱性:using ::foo::Bar。
只有当相对名称引用的是已经位于当前命名空间内部的名称时,它才是无歧义且不会被外部声明破坏的:
|
|
这遵循我们在上一节讨论过的同一逻辑。
如果一个名称位于兄弟命名空间中,例如 ::example::tools::Thing,该怎么办?你可以写 tools::Thing,也可以写 ::example::tools::Thing。完全限定名总是正确的,但使用相对名称也可能合适。请自行判断。
避免许多这类问题的廉价方式,是不要在你的项目中使用和流行顶层命名空间(例如 util)相同的命名空间;风格指南明确推荐这种做法。
演示
下面的代码展示了两种失败模式。
helper.h:
|
|
some_feature.h:
|
|
你的代码:
|
|
匿名命名空间
放在匿名命名空间中的 using 声明可以从外围命名空间访问,反之亦然。如果你在文件顶部已经有匿名命名空间,优先把所有别名都放在那里。从该匿名命名空间内部看,你会获得一点额外稳健性,避免与外围命名空间中声明的东西冲突。
|
|
非别名名称
到目前为止,我们一直在谈远处名称的局部别名。但如果我们想直接使用名称,而完全不创建别名呢?应该写 util::Status 还是 ::util::Status?
没有明显答案。和前面讨论的别名声明不同,别名声明出现在文件顶部,离实际代码很远;而直接使用名称会影响代码的局部可读性。虽然相对限定名未来确实可能坏掉,但使用绝对限定也有显著成本。前导 :: 造成的视觉杂乱可能会分散注意力,不值得用来换取额外稳健性。在这种情况下,请自行判断偏好的风格。见 TotW 130。
致谢
所有功劳都归 Roman Perepelitsa (romanp@google.com),他最初在邮件列表讨论中建议了这种风格,并贡献了许多修正和妙语。不过,所有错误都是我的。