"using namespace" 究竟是做什么的?

21

以下 C++ 测试代码无法链接(gcc 4.9.2,binutils 2.25)。错误为In function 'main': undefined reference to 'X::test'

01: #include <string>
02: #include <iostream>
03:
04: namespace X
05: {
06:     extern std::string test;
07: };
08:
09: using namespace X;
10: std::string test = "Test";
11:
12: int main()
13: {
14:    std::cout << X::test << std::endl;
15: }

由于第09行的原因,我原本期望第10行定义了在第06行声明的X::test变量。我认为实际上是在全局命名空间中声明并定义了一个不相关的test变量,从而导致链接错误。

问题:请问有人能够解释一下我的期望为何不正确,以及到底发生了什么?

非答案:

  • 我可以通过将第10行更改为std::string X::test = "Test";来进行链接。
  • 我本来就不应该使用"using namespace"。

由于您在命名空间 extern 中声明,它会在命名空间之外查找。它没有在命名空间内定义,编译器无法找到它,因此出现“未定义引用”的错误。请问您:为什么需要使用 extern - wouter140
3
@wouter140: extern与“在命名空间外寻找某物”无关。它只是意味着“这个定义在别处(extern)”。 - DevSolar
1
@curiousguy 字面上意思是“别处”,或者实际上是其他任何地方。这只是一个声明,不是定义。 - Angew is no longer proud of SO
1
@curiousguy,就像Notepad++一样(这里提到了一个非Unix主要世界的编辑器)。 - Angew is no longer proud of SO
@curiousguy:我知道我在提问并希望社区能够友善地提供帮助。但是,开发人员不使用正确的工具来完成工作难道不应该是他们自己的问题而不是我的吗?按照在线示例/教程往往需要具有此功能的编辑器。话虽如此,我想让我的问题更好:您是否有在SO中更好格式化的代码示例链接? - marcv81
显示剩余2条评论
3个回答

29
using namespace X;指令会使得命名空间X中的名称在包含该指令的命名空间内可见。也就是说,当在该作用域查找一个名称n时,可以找到X::n。但是,只有在编译器需要查找它时才会查找。
在你的示例中,这个声明:
std::string test = "Test";

全局命名空间内的内容就像现有的一样很有意义。名称test只是像其他声明一样简单引入,无需在任何地方查找。

这将是一个完全不同的问题:

namespace X
{
  struct C
  {
    static std::string test;
  };
}

using namespace X;
std::string C::test = "Test";
在这段代码中,编译器需要知道C的含义,以使得定义C::test有意义。因此,它会查找C的名称,并且由于存在using指令,可以找到X::C

1
非常准确的答案。结构体的情况也非常有趣。谢谢。 - marcv81

9

using namespace表示您从指定的命名空间使用定义,但这并不意味着您定义的所有内容都被定义在您使用的命名空间中。

这种行为的逻辑相当简单。假设我们有以下示例:

namespace X
{
    extern string test;
};

namespace Y
{
    extern string test;
};

using namespace X;
using namespace Y;

string test = "value";

按照你的逻辑,编译器不知道应该在哪个命名空间中定义test,因此你需要明确地声明命名空间。在实际情况中,它被定义在全局命名空间中。

在你的特定情况下,你将test变量定义在X命名空间之外,在其中声明为extern。链接器寻找X::test的定义,但没有找到,结果就会出现这个错误。


1
我同意你的代码存在歧义。但我觉得你并没有成功地解释为什么在我的例子中编译器决定在全局命名空间中声明和定义一个新变量,而不是在我正在使用的命名空间中找到现有变量。 - marcv81
2
@marcv81:关键是编译器在定义标识符时根本不会“搜索”声明。 (标识符在定义之前不需要被声明。)语句std::string test = ...在当前命名空间中定义了一个变量test,而using namespace ...并不影响它;它只影响要搜索哪些命名空间的标识符。只有在引用标识符时才会“搜索”标识符--这是定义不需要的。这正是Alex试图表达的意思。 - DevSolar
3
更好的风格是相反的,总是写X::,永远不要写using namespace X;。后者实际上是一个挡箭牌,因为它从源代码中删除了信息。(当调试使用半打"using namespace"的源代码时,您无法确定特定标识符来自哪个命名空间,这真的很糟糕。如果命名空间名称太长而无法重复键入,请使用namespace short = very::long::namespace;short::identifier的方式。 - DevSolar
我喜欢的一种模式是 namespace vln = very::long::namespace; - OrangeDog
1
@DevSolar:永远不写using namespace是一个好的规则,但总是写X::并不是。限定名称会破坏ADL。在某些情况下,在本地作用域中应用using声明以将命名空间中的个别名称引入重载集合中要好得多,但让重载解析选择最终选择哪些名称。 - Ben Voigt
显示剩余2条评论

3

这里是在命名空间X中声明变量test的语句。

04: namespace X
05: {
06:     extern std::string test;
07: };

这并不是变量的定义。在获取其值之前,必须先定义该变量。

如果您初始化变量,也可以将此声明作为定义。例如

04: namespace X
05: {
06:     extern std::string test = "Test";
07: };

在这种情况下,代码将成功编译。
在这个语句中,
14:    std::cout << X::test << std::endl;

有一个对限定名称 X::test 的访问。编译器按照变量中指定的方式在命名空间 X 中搜索此名称并找到声明。现在它需要获取变量的值,但无法找到其定义。

在这个语句中,

10: std::string test = "Test";

因为变量 test 是在任何明确指定的命名空间之外声明的,所以它在全局命名空间中被声明和定义。

您可以如下写:

10: std::string X::test = "Test";
                ^^^^^^^

代替
10: std::string test = "Test";

如果您想定义在命名空间X中声明的变量。

至于using指令,它会将指定的命名空间中声明的名称引入到使用该指令的命名空间中。

例如,如果要编写未限定名称test的using语句。

14:    std::cout << test << std::endl;
                    ^^^^^

如果使用了using指令,那么就会出现歧义,因为这个名称既可以引用X::test,也可以引用::test


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接