1

为什么有些语言,如 C++ 和 Python,即使不存在歧义,也需要指定对象的命名空间?我知道这有后门,比如using namespace x在 C++ 或from x import *Python 中。但是,当只有一个可访问的命名空间包含给定的标识符并且不存在歧义时,我无法理解不希望语言“做正确的事情”背后的理由。对我来说,这只是不必要的冗长和违反 DRY,因为你被迫指定编译器已经知道的东西。

例如:

import foo  # Contains someFunction().

someFunction()  # imported from foo.  No ambiguity.  Works.

比。

import foo  # Contains someFunction()
import bar  # Contains someFunction() also.

# foo.someFunction or bar.someFunction?  Should be an error only because
# ambiguity exists.
someFunction() 
4

8 回答 8

11

一个原因是为了防止在以后更改代码时(或对于外部模块/库,当其他人更改它时)意外引入冲突。例如,在 Python 中,您可以编写

from foo import *
from bar import *

如果您知道这些模块foo并且bar没有任何具有相同名称的变量,则不会发生冲突。但是,如果在以后的版本中同时包含foobar包含名为 的变量rofl怎么办?然后会在你不知情的情况下bar.rofl掩盖。foo.rofl

我还希望能够查看文件顶部并准确查看正在导入的名称以及它们的来源(当然,我说的是 Python,但同样的推理也适用于 C++) .

于 2009-02-12T01:04:47.403 回答
11

Python 认为“显式优于隐式”。(输入import thispython解释器)

另外,假设我正在阅读某人的代码。也许是你的代码;也许这是我六个月前的代码。我看到对bar(). 函数从何而来?我可以在文件中查找 a def bar(),但如果我没有找到它,那该怎么办?如果 python 通过导入自动找到第一个可用的 bar(),那么我必须搜索导入的每个文件才能找到它。多么痛苦!如果函数查找通过导入层次结构递归呢?

我宁愿看zomg.bar();这告诉我函数的来源,并确保如果代码更改(除非我更改zomg模块),我总是得到相同的函数。

于 2009-02-12T01:10:48.870 回答
5

问题是关于抽象和重用:你真的不知道未来是否会有歧义

例如,在一个项目中设置不同的库是很常见的,只是为了发现它们都有自己的字符串类实现,称为“字符串”。然后,您的编译器会抱怨如果库没有封装在单独的名称空间中,则会出现歧义。

然后,通过指定您想在每个特定指令或上下文(阅读:范围)中使用的实现(如标准 std::string 之一)来避开这种歧义是一种令人愉快的乐趣。

如果你认为它在特定的上下文中很明显(阅读:在特定的函数中或C++ 中的.cpp ,python 中的.py 文件- 从不在 C++ 头文件中)你只需要表达自己并说“它应该是显而易见的”,添加“使用命名空间”指令(或导入 *)。直到编译器抱怨,因为它不是。

如果您在特定范围内使用 using,则根本不会违反 DRY 规则。

于 2009-02-12T01:20:32.920 回答
4

有些语言的编译器试图“做正确的事”——我想到了 Algol 和 PL/I。它们不再存在的原因是编译器非常不擅长做正确的事情,但很擅长做错的事情,只要有一半的机会!

于 2009-02-12T01:06:29.257 回答
1

该规则追求的理想是使创建可重用组件变得容易 - 如果您重用您的组件,您只是不知道哪些符号将在客户端使用的其他命名空间中定义。因此,该规则迫使您就您尚不了解的进一步定义明确表示您的意图。

然而,C++ 还没有达到这个理想,主要是因为 Koenig 查找。

于 2009-02-12T01:10:07.250 回答
1

这真的是正确的吗?

如果我有两种类型 ::bat 和 ::foo::bar 怎么办

我想引用 bat 类型但不小心按了 r 键而不是 t (它们彼此相邻)。

然后编译器在不给我警告的情况下搜索每个命名空间以找到 ::foo::bar 是“正确的事情”吗?

或者,如果我在整个代码库中都使用“bar”作为“::foo::bar”类型的简写怎么办。然后有一天我包含了一个定义 ::bar 数据类型的库。突然之间,以前没有的地方出现了歧义。突然间,“正确的事情”变得错误了。

在这种情况下,编译器要做的正确事情是假设我的意思是我实际编写的类型。如果我写 bar 没有命名空间前缀,它应该假设我指的是全局命名空间中的类型 bar。但如果它在我们假设的场景中这样做,它会改变我的代码引用的类型,甚至不会提醒我。

或者,它可能会给我一个错误,但是来吧,这太荒谬了,因为即使使用当前的语言规则,这里也不应该有歧义,因为其中一种类型隐藏在我没有的命名空间中指定,所以不应该考虑。

另一个问题是编译器可能不知道存在哪些其他类型。在 C++ 中,定义的顺序很重要。

在 C# 中,类型可以在单独的程序集中定义,并在您的代码中引用。编译器如何知道另一个程序集中不存在同名的另一种类型,只是在不同的命名空间中?它如何知道以后不会将一个程序添加到另一个程序集中?

正确的做法是做给程序员带来最少令人讨厌的惊喜的事情。根据不完整的数据来猜测程序员通常是不正确的做法。

大多数语言都为您提供了一些工具来避免必须指定命名空间。

在 c++ 中,您有“使用命名空间 foo”以及 typedef。如果您不想重复命名空间前缀,那就不要。使用该语言提供的工具,因此您不必这样做。

于 2009-02-12T01:31:41.410 回答
0

这一切都取决于您对“正确的事情”的定义。如果只有一个匹配项,编译器猜测您的意图是否正确?

双方都有争论。

于 2009-02-12T01:01:40.063 回答
0

有趣的问题。在 C++ 的情况下,如我所见,如果编译器在发生冲突时立即标记错误,那么这可能导致的唯一问题是:

所有 C++ 命名空间的自动查找将删除隐藏库代码内部部分名称的能力。

库代码通常包含不打算对“外部世界”可见的部分(类型、函数、全局变量)。正是因为这个原因,C++ 有未命名的命名空间——以避免“内部部分”堵塞全局命名空间,即使这些库命名空间是使用using namespace xyz;.

示例:假设 C++确实进行了自动查找,并且 C++ 标准库的特定实现包含一个内部帮助函数std::helper_func(). joe::helper_func()假设用户 Joe使用不包含的不同库实现开发了一个包含函数的应用程序std::helper_func(),并使用对 的非限定调用来调用他自己的方法helper_func()。现在 Joe 的代码可以在他的环境中正常编译,但是任何其他尝试使用第一个库实现来编译该代码的用户都会遇到编译器错误消息。因此,使 Joe 的代码可移植所需的第一件事是插入适当的using声明/指令或使用完全限定的标识符。换句话说,自动查找对可移植代码没有任何好处。

诚然,这似乎不是一个经常出现的问题。但是由于键入显式using声明/指令(例如using namespace std;)对大多数人来说并不是什么大问题,完全解决了这个问题,并且无论如何都是可移植开发所必需的,因此使用它们(呵呵)似乎是一种明智的做事方式。

注意: 正如 Klaim 所指出的,在任何情况下,您都不会希望依赖头文件中的自动查找,因为这会立即阻止您的模块与包含冲突名称的任何模块同时使用。(这只是为什么你不在using namespace xyz;C++ 中的内部标题的逻辑扩展。)

于 2009-02-12T05:15:26.897 回答