为什么要将类型放在未命名的命名空间中?

时间:2015-08-19 17:58:37

标签: c++ unnamed-namespace

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

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

3 个答案:

答案 0 :(得分:13)

您希望将未命名的命名空间以外的本地类型放在哪里?类型不能具有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();
}

如果您关联这三个翻译单元,则helperfoo.cpp的{​​{1}}定义不匹配。编译器/链接器不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束被称为违反“一个定义规则”(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的每个定义应由相同的令牌序列组成;和   [...]

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

答案 1 :(得分:4)

  

那么在未命名的命名空间中使用put类型是什么意思?

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

例如,我经常在未命名的命名空间中使用两个类 - 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节开始:

  

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

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

struct Initializer
{
   Initializer() {}
};

对于像这样的简单情况,这是可以的,因为实现是相同的。如果内联实现不同,则程序将受到未定义的行为。

答案 2 :(得分:0)

我回答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

<强>哇!它有效!

事实证明, 对于在未命名的命名空间中定义类非常重要,这样当不同翻译单元中的两个类名相同时,您就可以从中获得正确的功能。 现在关于为什么就是这种情况,我还没有做过任何研究(也许有人可以帮忙吗?)所以我无法确切地告诉你。我纯粹是从实际角度回答。

我怀疑的是,虽然它是 true ,但C结构对于翻译单元来说确实是本地的,但它们与类有点不同,因为c ++中的类通常具有分配给它们的行为。行为意味着功能,而且我们知道,翻译单元的功能

这只是我的假设。