在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
以下是一些原因:
-
为了避免双重包含,在头文件本身中添加一个通常的include guard就足够了。 它做得很好。 另外在包含的地方包括守卫只是把代码弄乱并降低可读性。
-
它增加了不必要的依赖性。 如果在头文件中更改include guard,则必须在包含头文件的所有位置更改它。
-
在整个编译/链接过程中,这绝对不是最昂贵的操作,因此几乎不能缩短总编译时间。
-
任何有价值的编译器都已经优化了文件范围的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 ++)支持它。
所以无论什么纯粹主义的论证都在传播,它运作得更好:
- 快速
- 没有维护,没有神秘的不包含的麻烦,因为你复制了一面旧国旗
- 明显含义与隐藏线的单行在文件中传播
所以简单地说:
#pragma once
在文件的开始,就是这样。 优化,可维护,并准备好去。