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

Posted

技术标签:

【中文标题】为啥要将类型放在未命名的命名空间中?【英文标题】:Why should types be put in unnamed namespaces?为什么要将类型放在未命名的命名空间中? 【发布时间】:2015-11-13 03:58:06 【问题描述】:

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

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

Superiority of unnamed namespace over static? Unnamed/anonymous namespaces vs. static functions Why an unnamed namespace is a "superior" alternative to static?

【问题讨论】:

对 SO 的***近在咫尺,需要停止。 问题范围合理,可以回答 【参考方案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 结构确实是翻译单元的本地结构是 true,但它们与类有点不同,因为 C++ 中的类通常具有分配给它们的行为.行为意味着函数,正如我们所知,函数不是翻译单元本地的。

这只是我的假设。

【讨论】:

这是一个有趣的观察,我也很想知道为什么会这样。我想如果在早期的翻译单元中已经找到了函数::Test::talk 的实现,那么其他实现将被忽略是有道理的,但是显式声明未命名的命名空间会以什么方式改变这一点?至少不应该引起警告?【参考方案2】:

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

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

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

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

【讨论】:

我的意思是,类型 Initializer 和 Helper 不需要位于未命名的命名空间中。只有初始化变量可以。如果您在未命名的命名空间之外声明类型,您的代码将完全相同。那么为什么要把它们放在里面呢? 如果多个文件在未命名的命名空间之外有InitializerHelper,在链接时会发生冲突。 不,即使多个cpp文件在未命名的命名空间之外有Initializer和Helper,也不会有任何冲突!试试吧。类型是 cpp 文件的本地类型,在外部不可见。 @FrancisXavier 并非所有有效(或似乎有效)的东西都一定是正确的。同一类型的两个不同定义违反了一个定义规则。这样的程序具有未定义的行为,任何事情都可能发生。例如,如果对此类成员函数的调用未内联,则链接器可以静默删除其中一个冲突定义并将所有调用重定向到一个主体。 @FrancisXavier 如果没有在同一个未命名命名空间中的类型声明,您不能将构造函数定义放在未命名命名空间中。【参考方案3】:

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

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

【讨论】:

我刚刚尝试过:我将相同的类声明放在多个文件的全局命名空间中,但没有出现任何错误。似乎这些类型被视为翻译单元的本地类型。那么另外将它们放入未命名的命名空间似乎没有任何价值? @FrancisXavier 尝试使用非平凡的默认构造函数:struct helper helper() std::cout &lt;&lt; __FILE__ &lt;&lt; "\n"; ; 在不同的文件中定义两个helper,从相应文件中的函数构造每个实例,从 @ 调用这两个函数987654328@。编译没有优化。 helper::helper() 将有两个符号,并且由于它(隐式)内联,因此将丢弃一个。剩下的一个将从两个函数中调用(linux x64 上的 gcc 4.9.2,不能保证这种行为)。 是的,当然。我对 UB 做了一点提示,但我发表评论的主要原因当然是展示一个意外发生的例子。 - 其他不好的事情发生,例如当该类型用作函数的参数或返回类型时(甚至间接地用作以这种方式使用的类型的数据成员)。 -- 编辑:d'oh,我应该在评论之前进一步向下滚动并阅读 R Sahu 的答案。 @dyp 注册。你的第一条评论。在那种特殊情况下,他实际上并没有违反 ODR,是吗?鉴于helper 的定义将逐字逐句相同? 啊!我完全错过了__FILE__ 指令!我现在明白了。我也使用了松散的术语。我的意思是代币。

以上是关于为啥要将类型放在未命名的命名空间中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥未命名的命名空间是静态的“优越”替代品? [复制]

C#中类和命名空间是啥?为啥要用?啥时候用到?请高手详细说明

JAXB:为啥在生成的 xml 文档中未使用定义的命名空间前缀?

未命名的命名空间和 Visual C++ 链接器性能

类型命名空间未声明或不是简单类型

未命名的命名空间访问规则