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

Why should types be put in unnamed namespaces?

本文关键字:空间 未命名 类型 为什么      更新时间:2023-10-16

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

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

  • 未命名命名空间优于静态命名空间?
  • 未命名/匿名命名空间与静态函数
  • 为什么未命名的命名空间是"优越的";静态的替代方案?

除了未命名的名称空间,您希望将本地类型放在哪里?类型不能有像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.cppbar.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的每个定义应由相同的令牌序列组成;和[…]

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

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

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

例如,我经常在未命名的命名空间中使用两个类——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() {}
};

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

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

让我们做个测试…编译以下文件:

//main.cpp
#include <iostream>
#include "test.hpp"
class Test {
public:
     void talk() {
      std::cout<<"I'm test MAINn";
     }
};
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 2n";
      }
};

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 2n";
           }
     };
}
void testfunc() {
     Test t;
     t.talk();
}

重新编译并运行。输出应该是:

I'm test MAIN
I'm test 2

哇!它的工作原理!


事实证明,在未命名的名称空间内定义类非常重要,这样当不同翻译单元中的两个类名称相同时,您就可以从它们获得适当的功能。至于为什么是这种情况,我没有做过任何研究(也许有人可以在这里提供帮助?),所以我真的不能肯定地告诉你。我只是从实际的角度来回答。

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

这只是我的假设