在 C + + 中使用 double include 保护

所以我最近在我工作的地方进行了一次讨论,在这次讨论中,我质疑使用 双倍包含后卫对单个后卫的使用。我所说的 双重保护是这样的:

头文件“ Header _ a. hpp”:

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

在任何地方包含头文件时,无论是在头文件还是源文件中:

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

现在我明白了,在头文件中使用约束是为了防止多次包含已经定义的头文件,这是很常见的,并且有很好的文档说明。如果宏已经定义,编译器会将整个头文件视为“空白”,并防止重复包含。很简单。

The issue I don't understand is using #ifndef __HEADER_A_HPP__ and #endif around the #include "header_a.hpp". I'm told by the coworker that this adds a second layer of protection to inclusions but I fail to see how that second layer is even useful if the first layer absolutely does the job (or does it?).

我能想到的唯一好处是,它可以直接阻止链接器找到文件。这是为了提高编译时间(没有提到这是一个好处) ,还是有什么其他的东西在这里起作用,我没有看到?

10504 次浏览

我非常肯定,添加另一个包含后卫的做法是不好的,比如:

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

以下是一些原因:

  1. 为了避免双重包含,在头文件本身内部添加一个通常的包含保护就足够了。它的工作做得很好。另一个包含在包含位置的保护只会混乱代码并降低可读性。

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

  3. 与整个编译/链接过程相比,这肯定不是最昂贵的操作,因此很难减少总的构建时间。

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

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

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

它可以防止 预处理器不厌其烦地寻找文件,但是如果定义了约束,那就意味着它已经找到了文件。我怀疑,如果预处理时间减少了,除了在最病态递归包括怪物的影响将是相当微小的。

它的缺点是,如果曾经更改过保护(例如由于与另一个保护发生冲突) ,则必须更改 include 指令之前的所有条件语句,以便它们能够正常工作。如果其他东西使用了前面的保护,那么条件句必须改变,头文件本身才能正常工作。

P.S. __HEADER_A_HPP__是一个保留给实现的符号,因此它不是您可以定义的。给警卫起个别的名字。

标题文件中放置包含保护的原因是为了防止头部的内容被多次拉入翻译单元。这是很正常的,长期以来的惯例。

来源文件中放置冗余的 include 警卫的原因是为了避免打开被包含的头文件,而且回到过去,这样做可以大大加快编译速度。如今,打开文件的速度比以前快多了; 此外,编译器非常善于记住他们已经看过的文件,并且他们理解 include Guard 的习惯用法,所以可以自己弄清楚他们不需要再次打开文件。这有点手忙脚乱,但底线是,这个额外的层不再需要了。

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

更传统(大型机)平台上的老编译器(我们在这里讨论的是2000年代中期)过去没有其他答案中描述的优化,所以它确实用来显著减慢预处理时间,不得不重新阅读已经包含的头文件(记住,在一个庞大的、单一的、企业级的项目中,你将包含大量的头文件)。作为一个例子,我看到的数据显示,一个文件有256个头文件,每个头文件包括 AIX 编译器 VisualAge C + + 6上相同的256个头文件(可以追溯到2000年代中期)。这是一个相当极端的例子,但是这种加速确实合情合理。

然而,所有现代编译器,即使是在 AIX 和 Solaris 这样的大型机平台上,对头部包含的优化程度都足够高,以至于目前的差异真的可以忽略不计。因此,再也没有什么好的理由去拥有它们了。

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

尽管有人反对它,但实际上“ # 杂注一次”工作得很好,主编译器(gcc/g + + ,vc + +)支持它。

因此,无论人们传播什么纯粹主义的论证,效果都要好得多:

  1. 快点
  2. 没有维护,没有麻烦与神秘的非包含,因为你复制了一个旧的旗帜
  3. 有明显含义的单行与文件中传播的隐晦行

简单来说:

#pragma once

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