使用double包括C++中的守卫

sh3*_*fme 71 c++ macros linker

所以我最近讨论了我的工作,其中我质疑使用双重防守对一名后卫的影响.双防护的意思如下:

头文件"header_a.hpp":

#ifndef __HEADER_A_HPP__
#define __HEADER_A_HPP__
...
...
#endif
Run Code Online (Sandbox Code Playgroud)

将头文件包含在头文件或源文件中的任何位置时:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif
Run Code Online (Sandbox Code Playgroud)

现在我明白在头文件中使用guard是为了防止多次包含已经定义的头文件,这是常见的并且有很好的文档记录.如果已经定义了宏,则编译器会将整个头文件视为"空白",并防止双重包含.很简单.

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

我能想到的唯一好处是它可以直接阻止链接器找到文件.这是否意味着改善编译时间(没有提到作为一个好处),或者在这里有什么其他工作我没有看到?

Edg*_*jān 105

我很确定添加另一个包含守卫是不好的做法:

#ifndef __HEADER_A_HPP__
#include "header_a.hpp"
#endif
Run Code Online (Sandbox Code Playgroud)

以下是一些原因:

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

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

  3. 它绝对不是比较整个编译/链接过程最昂贵的操作,因此它几乎不能减少总的构建时间.

  4. 任何有价值的编译器都 已经优化了文件范围的包含保护.

  • `如果你在头文件中更改包含保护,你必须在包含标题的所有地方更改它.... ....从技术上来说,不,你不这样做,但我认为这种情况更进一步证明了这一点. (2认同)

Pet*_*ker 49

文件中放入包含警戒的原因是为了防止标题的内容被多次拉入翻译单元.这是正常的,长期存在的做法.

放置冗余的原因包括文件中的保护是为了避免必须打开包含的头文件,并回到过去可能会大大加快编译的时间.这些天,打开文件比以前快得多; 此外,编译器非常聪明地记住他们已经看过哪些文件,并且他们理解包含守护成语,因此可以自己弄清楚他们不需要再次打开文件.这有点挥手,但最重要的是不再需要这个额外的层了.

编辑:这里的另一个因素是,编译C++是远远超过编译C更复杂,所以需要远远更长的时间,使时间花在开放包括文件的需要来编译转换单元中,时间更小,更显著部分.

  • 这是一个用一些文档备份你的"挥手"的链接;-):https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html (7认同)
  • @ sh3rifme是的.把这句话读成"你可以把所有的东西放在控制的`#if`-`#endif`对之外,而不禁用优化就是空白和评论." 但是[你不应该使用`#pragma once`](/sf/ask/80075551/#34884735). (3认同)

eer*_*ika 22

我能想到的唯一好处是它可以直接阻止链接器找到文件.

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

它可以防止预处理器打扰找到文件,但如果定义了防护,这意味着它已经找到了该文件.我怀疑如果预处理时间完全减少,效果将是非常小的,除了在病理学上最复杂地包括怪物.

它有一个缺点,如果警卫被改变(例如由于与另一个警卫的冲突),必须改变包含指令之前的所有条件,以使它们起作用.如果其他东西使用了前一个后卫,则必须更改条件,以使include指令本身正常工作.

PS __HEADER_A_HPP__是为实现保留的符号,因此它不是您可以定义的内容.为警卫使用另一个名字.

  • @ sh3rifme标准表示包含两个连续下划线的所有标识符都保留给实现.还有其他保留标识符.我建议你熟悉这些规则. (13认同)
  • @ sh3rifme:保留给实现,可能包括在包含`header_a.hpp`时自动定义`__HEADER_A_HPP__`的用法.这当然打破了你的标题保护,假设它只在_second_行上定义. (7认同)

Muz*_*zer 17

更传统(大型机)平台上的旧编译器(我们在这里谈到2000年代中期)并不习惯在其他答案中描述优化,因此它确实用于显着减慢必须重新读取头文件的预处理时间已经被包含在内(考虑到一个庞大的,整体的企业项目,你将包括很多头文件).作为一个例子,我已经看到数据表明一个文件有26倍的加速,文件有256个头文件,每个文件包括VisualAge C++ 6 for AIX编译器(可追溯到2000年代中期)的相同256个头文件.这是一个相当极端的例子,但这种加速确实加起来.

但是,所有现代编译器,甚至在大型机平台(如AIX和Solaris)上,都会对包含的头文件进行足够的优化,这些天差异确实可以忽略不计.因此没有充分理由再拥有这些.

然而,这确实解释了为什么一些公司仍然坚持这种做法,因为相对最近(至少在C/C++代码库年龄方面),对于非常大的单片项目来说仍然是值得的.

  • 哪个还在页面缓存中.即使256个文件中的每一个都是128 kiB,这只是32 MiB的数据,总共需要从内核空间复制到用户空间的8 GiB数据.现代硬件可以在不到一秒的时间内完成.如果编译器在此操作上花费很长时间,那么编译器的错误就是100%. (2认同)

Ber*_*ril 8

虽然有人反对它,但在实践中'#pragma once'完美运行,主编译器(gcc/g ++,vc ++)支持它.

因此,无论人们传播什么样的纯粹论证,它都会更好:

  1. 快速
  2. 没有维护,没有神秘的非包容的麻烦,因为你复制了一面旧旗
  3. 具有明显含义的单行与在文件中传播的神秘线条相对应

所以简单地说:

#pragma once
Run Code Online (Sandbox Code Playgroud)

在文件的开头,就是这样.优化,可维护,随时可用.

  • @Kevin但主要的一点是,警卫不容易出错,并且99.9%的pragma曾经足够让你免于担心这个古老的知识.与pragma相比 - 它们真的很神秘而且......很慢.;)慢只是因为你需要写它们,维护和阅读. (3认同)
  • 并不适用于所有编译器. (2认同)