10

我了解使用未命名的命名空间使函数和变量具有内部链接。头文件中不使用未命名的命名空间;只有源文件。源文件中声明的类型不能在外部使用。那么将类型放入未命名的命名空间有什么用呢?

请参阅这些链接,其中提到类型可以放在未命名的命名空间中:

4

3 回答 3

17

除了未命名的命名空间之外,你想把本地类型放在哪里?类型不能有像static. 如果它们不为公众所知,例如,因为它们是在标头中声明的,那么本地类型的名称很可能会发生冲突,例如,当两个翻译单元定义具有相同名称的类型时。在这种情况下,您最终会违反 ODR。在未命名的命名空间内定义类型会消除这种可能性。

再具体一点。考虑你有

// file demo.h
int foo();
double bar();

// file foo.cpp
struct helper { int i; };
int foo() { helper h{}; return h.i; }

// file bar.cpp
struct helper { double d; }
double bar() { helper h{}; return h.d; }

// file main.cpp
#include "demo.h"
int main() {
     return foo() + bar();
}

如果链接这三个翻译单元,则helperfromfoo.cpp和的定义不匹配bar.cpp。编译器/链接器不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束称为违反“单一定义规则”(ODR)。任何违反 ODR 规则的行为都会导致未定义的行为。

鉴于评论似乎需要更有说服力。标准的相关部分是 3.2 [basic.def.odr] 第 6 段:

类类型(第 9 条)、枚举类型(7.2)、带外部链接的内联函数(7.1.2)、类模板(第 14 条)、非静态函数模板(14.5.6)可以有多个定义、类模板的静态数据成员 (14.5.1.3)、类模板的成员函数 (14.5.1.1) 或在程序中未指定某些模板参数的模板特化 (14.7、14.5.5),前提是每个定义出现在不同的翻译单元中,并且定义满足以下要求。给定这样一个名为 D 的实体在多个翻译单元中定义,则 D 的每个定义应由相同的标记序列组成;和 [...]

有很多进一步的限制,但“应由相同的令牌序列组成”显然足以排除例如上述演示中的定义是合法的。

于 2015-08-19T18:05:50.057 回答
4

那么将类型放入未命名的命名空间有什么用呢?

您可以创建简短、有意义的类,其名称可能在多个文件中使用,而不会出现名称冲突问题。

例如,我经常在未命名的命名空间中使用两个类 -InitializerHelper.

namespace
{
   struct Initializer
   {
      Initializer()
      {
         // Take care of things that need to be initialized at static
         // initialization time.
      }
   };

   struct Helper
   {
      // Provide functions that are useful for the implementation
      // but not exposed to the users of the main interface.
   };

   // Take care of things that need to be initialized at static
   // initialization time.
   Initializer initializer;
}

我可以在尽可能多的文件中重复这种代码模式,而无需名称InitializerHelper妨碍。

更新,以回应OP的评论

文件-1.cpp:

struct Initializer
{
   Initializer();
};

Initializer::Initializer()
{
}

int main()
{
   Initializer init;
}

文件 2.cpp:

struct Initializer
{
   Initializer();
};

Initializer::Initializer()
{
}

构建命令:

g++ file-1.cpp file-2.cpp

我收到有关Initializer::Initializer(). 请注意,该标准不要求链接器产生此错误。从第 3.2/4 节开始:

每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的准确定义;无需诊断。

如果函数是内联定义的,则链接器不会产生错误:

struct Initializer
{
   Initializer() {}
};

对于像这样的简单案例来说没关系,因为实现是相同的。如果内联实现不同,则程序会受到未定义行为的影响。

于 2015-08-19T18:09:26.953 回答
1

回答 OP 提出的问题我可能有点晚了,但由于我认为答案并不完全清楚,我想帮助未来的读者。

让我们尝试一个测试...编译以下文件:

//main.cpp
#include <iostream>
#include "test.hpp"

class Test {
public:
     void talk() {
      std::cout<<"I'm test MAIN\n";
     }
};

int main()
{
     Test t;
     t.talk();
     testfunc();    
}

//test.hpp
void testfunc();

//test.cpp
#include <iostream>

class Test {
public:
     void talk()
      {
           std::cout<<"I'm test 2\n";
      }
};


void testfunc() {
     Test t;
     t.talk();
}

现在运行可执行文件。你会期望看到:

I'm test MAIN
I'm test 2

你应该看到的想法是:

I'm test MAIN
I'm test MAIN

发生了什么?!?!!

现在尝试在“test.cpp”中的“Test”类周围放置一个未命名的命名空间,如下所示:

#include <iostream>
#include "test.hpp"

namespace{
     class Test {
     public:
      void talk()
           {
            std::cout<<"I'm test 2\n";
           }
     };
}

void testfunc() {
     Test t;
     t.talk();
}

再次编译并运行。输出应该是:

I'm test MAIN
I'm test 2

哇!有用!


事实证明,在未命名的命名空间中定义类非常重要,这样当不同翻译单元中的两个类名相同时,您可以从中获得正确的功能。现在至于为什么会这样,我还没有对此进行任何研究(也许有人可以在这里提供帮助?)所以我不能确定地告诉你。我纯粹从实际的角度来回答。

我怀疑的是,虽然C 结构确实是翻译单元的本地,但它们与类有点不同,因为 C++ 中的类通常具有分配给它们的行为。行为意味着函数,正如我们所知,函数不是翻译单元的本地函数。

这只是我的假设。

于 2017-09-26T08:42:31.447 回答