为什么 C++ 的“使用命名空间”会以这种方式工作?

Why does C++'s "using namespace" work the way it does?

提问人:Quuxplusone 提问时间:12/19/2020 最后编辑:TylerHQuuxplusone 更新时间:4/18/2022 访问量:1133

问:

所有学生都对 C++ 使用指令的行为感到惊讶。考虑以下代码片段 (Godbolt):

namespace NA {
    int foo(Zoo::Lion);
}
namespace NB {
    int foo(Zoo::Lion);
    namespace NC {
        namespace N1 {
            int foo(Zoo::Cat);
        }
        namespace N2 {
            int test() {
                using namespace N1;
                using namespace NA;
                return foo(Zoo::Lion());
            }
        }
    }
}

你可能会认为会叫 ;但实际上它最终调用了 .testNAfoo(Zoo::Lion)N1foo(Zoo::Cat)

原因是它实际上并没有将名称带入当前范围;它使它们进入 和 的最小共同祖先范围,即 。并且不会将名称从当前范围带入;它使它们进入 和 的最小共同祖先范围,即 。在这里,我画了一个漂亮的图表:using namespace NANANAN2::using namespace N1N1N1N2NC

Diagram

然后,正常的不合格查找按顺序“查找”树,顺序为 ––––,一旦在 中找到名称,就会停止。树上更高的 foos in 和 不会通过查找找到,因为它们已被 in 隐藏。testN2NCNB::fooNCNB::fooNC

我想我对这种机制的理解足以解释它是如何工作的。我不明白的是为什么。在设计C++命名空间时,为什么他们选择这种完全奇怪的机制,而不是更直接的机制,例如“名称被引入当前范围,就像使用声明一样”或“名称被引入全局范围”或“名称留在原地,在每个'使用'命名空间中进行单独的查找,然后合并在一起”?

为 C++ 选择这种特定机制时考虑了哪些因素?

我专门寻找对C++设计的参考——书籍、博客文章、WG21论文、D&EARM、反射器讨论,任何类似的东西。我特别不是在寻找“基于意见”的答案;我正在寻找设计该功能时指定的真正理由。

(我读过 The Design and Evolution of C++ (1994),第 17.4 节“命名空间”,但它甚至没有提到这个古怪的机制。D&E 说:“using 指令不会将名称引入本地范围;它只是使命名空间中的名称可访问。这是真的,但缺乏理由;我希望 Stack Overflow 能做得更好。

C++ 语言律师 C++98 名称查找 using 指令

评论

8赞 user4581301 12/19/2020
如果斯特鲁斯特鲁普不解释他的推理,那么推理只能被推断出来。我不确定你能得到一个完美的答案,但我非常愿意让这个问题保持开放,看看人们想出什么。
0赞 Wyck 12/19/2020
可以参与 ISO C++ 标准 - 讨论

答:

2赞 Tarun 12/28/2020 #1

引用C++入门(第 5 版)部分“18.2.2.使用命名空间成员”

using 指令引入的名称范围更为复杂 比使用声明时的名称范围。正如我们所看到的,使用 声明将名称置于与 using 相同的作用域中 声明本身。就好像 using 声明声明了 命名空间成员。

using 指令不声明本地别名。相反,它有效果 将命名空间成员提升到包含 命名空间本身和 using 指令。

using 声明和 using 声明之间的此范围差异 指令直接源于这两个设施的运作方式。在以下情况下 一个使用声明,我们只是让名称直接访问 本地范围。相反,using 指令使 命名空间可用 通常,命名空间可能包含定义 不能出现在本地范围内。因此,using 指令 被视为它出现在最近的封闭命名空间作用域中。

因此,与 using 声明相比,using 指令在作用域处理方面的这种差异是为了不阻止声明相同的名称。当发生此类名称冲突时,将允许这样做,但任何模棱两可的用法都必须明确指定预期的版本。

评论

0赞 Quuxplusone 12/29/2020
我可能会要求对此进行原创研究,但是,作者所说的“命名空间可能包含无法出现在本地范围内的定义”是什么意思?他们似乎在说有些东西是可以拉进来的,而不能通过使用声明拉进来。我唯一能想到的不能出现在本地范围的是模板,就像在 godbolt.org/z/EjT8Eo 中一样——但我们可以通过 using 声明或 using 指令很好地拖入模板。using namespace
0赞 Quuxplusone 12/29/2020
...我立即意识到,嵌套的命名空间声明可以被拉入,但不能被 !(命名空间别名有不同的语法:。不过,我仍然不明白“因此”这一点。using namespace N1using N1::N2namespace N2 = N1::N2