命名空间规范没有歧义

时间:2009-02-12 00:56:35

标签: c++ python namespaces language-design

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

例如:

import foo  # Contains someFunction().

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

Vs以上。

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

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

8 个答案:

答案 0 :(得分:11)

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

from foo import *
from bar import *

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

我也希望能够查看文件的顶部并确切地查看正在导入的名称以及它们来自哪里(当然,我正在谈论Python,但同样的推理可能适用for C ++)。

答案 1 :(得分:11)

Python认为'显式优于隐式'。 (将import this输入python解释器)

另外,说我正在读某人的代码。也许这是你的代码;也许这是我六个月前的代码。我看到bar()的引用。功能来自哪里?我可以查看文件中的def bar(),但是如果我找不到它,那么呢?如果python自动找到通过导入可用的第一个bar(),那么我必须搜索导入的每个文件以找到它。太痛苦了!如果函数查找通过导入heirarchy进行递归会怎么样?

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

答案 2 :(得分:4)

有些语言编译器试图“做正确的事” - Algol和PL / I浮现在脑海中。他们不再存在的原因是编译器做正确的事情是非常糟糕的,但是如果有一半机会的话,非常擅长做错的事情!

答案 3 :(得分:4)

问题在于抽象和重用:你真的不知道将来是否会出现歧义

例如,在项目中设置不同的库以发现它们都有自己的字符串类实现(称为“字符串”)是很常见的。 如果库没有封装在不同的命名空间中,那么编译器会抱怨存在歧义。

通过指定您希望在每个特定指令或上下文(读取:范围)中使用的实现(如标准的std :: string one)来避免这种歧义,这是一种愉快的乐趣。

如果您认为在特定情境中显而易见(阅读:在c ++的特定函数或.cpp 中, .py文件在python中 - 从不在C ++头文件中)你只需要表达自己并说“它应该是显而易见的”,添加“using namespace”指令(或import *)。直到编译器抱怨,因为它不是。

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

这真的是对的吗?

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

,该怎么办?

我想引用蝙蝠类型但是不小心碰到了r键而不是t(它们就在彼此旁边)。

编译器然后搜索每个命名空间来查找:: foo :: bar而不给我一个警告,这是“正确的事”吗?

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

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

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

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

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

正确的做法是为程序员提供最少的令人讨厌的惊喜。对基于不完整数据的程序员进行二次猜测通常不是正确的做法。

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

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

答案 6 :(得分:0)

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

双方都有争论。

答案 7 :(得分:0)

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

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

库代码通常包含从不打算对“外部世界”可见的部分(类型,函数,全局变量)。出于这个原因,C ++具有未命名的命名空间 - 避免“内部部件”堵塞全局命名空间,即使这些库命名空间是使用using namespace xyz;显式导入的。

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

不可否认,这似乎不是一个可能经常出现的问题。但是因为键入显式using声明/指令(例如using)对大多数人来说并不是一件大事,所以完全解决了这个问题,无论如何都需要便携式开发,使用它们(heh)似乎一种明智的做事方式。

注意:正如Klaim指出的那样,你永远不会在任何情况下想要依赖头文件中的自动查找,因为这会立即阻止你的模块与包含冲突名称的任何模块同时使用。 (这只是你不能在C ++中使用using namespace std;内部标记的原因的逻辑扩展。)