为什么类型应该放在未命名的命名空间中?

Why should types be put in unnamed namespaces?

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

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

除未命名命名空间外,您想将本地类型放在哪里?类型不能有像 static 这样的 linkage 说明符。如果它们不是公开的,例如,因为它们是在 header 中声明的,本地类型的名称很可能会发生冲突,例如,当两个翻译单元定义具有相同名称的类型时。在这种情况下,您最终会违反 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();
}

如果您 link 这三个翻译单元,您的 helper 来自 foo.cppbar.cpp 的定义不匹配。 compiler/linker 不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束称为违反 "one definition rule" (ODR)。任何违反 ODR 规则的行为都会导致未定义的行为。

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

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and [...]

还有很多进一步的限制,但 "shall consist of the same sequence of tokens" 显然足以排除例如上面演示中的定义是合法的。

So what's the use of putting types in unnamed namespaces?

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

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

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

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

struct Initializer
{
   Initializer() {}
};

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

我回答 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" class 周围放置一个未命名的命名空间,如下所示:

#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

哇!有效!


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

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

这只是我的假设。