在C ++中使用double包含警卫

所以我最近在一个讨论中,我在哪里工作,在那里我质疑使用一个单一的后卫一个双重的后卫。 我的意思是双重后卫如下:

头文件“header_a.hpp”:

#ifndef __HEADER_A_HPP__ #define __HEADER_A_HPP__ ... ... #endif 

将头文件包含在头文件或源文件中时:

 #ifndef __HEADER_A_HPP__ #include "header_a.hpp" #endif 

现在我明白,在头文件中使用guard是为了防止多个包含已经定义的头文件,这是常见的和有据可查的。 如果macros已经被定义,整个头文件被编译器视为“空白”,并且阻止了双重包含。 很简单。

我不明白的问题是在#include "header_a.hpp"周围使用#ifndef __HEADER_A_HPP__#endif 。 同事告诉我,这为夹杂物增加了第二层保护,但是如果第一层绝对地完成了工作(或者是做了这个工作),我看不出第二层如何是有用的。

我能想到的唯一好处就是它彻底阻止了连接器无法find文件。 这是否意味着提高编译时间(这不是提到的好处),还是在这里有其他工作,我没有看到?

我很确定,join另外一名包括后卫的球员是不好的做法:

 #ifndef __HEADER_A_HPP__ #include "header_a.hpp" #endif 

以下是一些原因:

  1. 为了避免双重包含,在头文件本身中添加一个通常的include guard就足够了。 它做得很好。 另外在包含的地方包括守卫只是把代码弄乱并降低可读性。

  2. 它增加了不必要的依赖性。 如果在头文件中更改include guard,则必须在包含头文件的所有位置更改它。

  3. 在整个编译/链接过程中,这绝对不是最昂贵的操作,因此几乎不能缩短总编译时间。

  4. 任何有价值的编译器都已经优化了文件范围的include-guard 。

文件中包含守护的原因是为了防止头部的内容被多次拉入翻译单元。 这是正常的,历史悠久的做法。

文件中放置冗余的原因是为了避免打开正在包含的头文件,并回到过去的时代,这可以显着加快编译速度。 现在,打开一个文件比以前要快得多, 而且,编译器在记住他们已经看到的文件的时候非常聪明,而且他们理解了包含守护语,所以可以自己弄清楚他们不需要再次打开文件。 这是一个挥手,但底线是这个额外的层不再需要了。

编辑:这里的另一个因素是编译C ++要比编译C复杂得多,所以需要花费更长的时间,使得打开包含文件花在编译翻译单元上的时间更less,更不重要。

我能想到的唯一好处就是它彻底阻止了连接器无法find文件。

链接器不会受到任何影响。

它可以防止预处理器打扰查找文件,但是如果定义了防护,那意味着它已经find了文件。 我怀疑,如果预处理时间完全减less,除了最大的病态recursion包含的怪物之外,效果将是非常小的。

它的不足之处在于,如果警卫发生了变化(例如与另一名警卫发生冲突),则必须更改包含指令之前的所有条件才能使其工作。 如果别的东西使用了前面的警卫,那么必须改变条件才能使include指令本身正常工作。

PS __HEADER_A_HPP__是一个保留给实现的符号,所以它不是你可以定义的东西。 为守卫使用另一个名字。

在更传统的(大型机)平台(我们正在谈论2000年代中期)的旧版编译器并没有使用其他答案中描述的优化,所以它确实用于显着减慢预处理时间,不得不重新读取头文件(已经包含在一个庞大的,整体的,企业级项目中,你将会包括很多头文件)。 举例来说,我已经看到数据表明,在VisualAge C ++ 6 for AIX编译器(从2000年代中期开始),每个文件有256个头文件,每个头文件包含相同的256个头文件。 这是一个相当极端的例子,但这种加速确实加起来了。

但是,即使在大型机平台(如AIX和Solaris)上,所有现代编译器都会对头文件包含进行足够的优化,而这些差异真的是微不足道的。 所以没有什么理由再有这些了。

然而,这却解释了为什么一些公司仍然坚持这种做法,因为相对最近(至less在C / C ++代码库时代),对于非常庞大的整体项目来说,这仍然是值得的。

尽pipe有人反对,但实际上'#pragma once'完美地工作,主编译器(gcc / g ++,vc ++)支持它。

所以无论什么纯粹主义的论证都在传播,它运作得更好:

  1. 快速
  2. 没有维护,没有神秘的不包含的麻烦,因为你复制了一面旧国旗
  3. 明显含义与隐藏线的单行在文件中传播

所以简单地说:

 #pragma once 

在文件的开始,就是这样。 优化,可维护,并准备好去。