在c++中使用双重保护

时间:2021-07-04 04:54:48

So I recently had a discussion where I work, in which I was questioning the use of a double include guard over a single guard. What I mean by double guard is as follows:

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

Header file, "header_a.hpp":

头文件,“header_a.hpp”:

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

When including the header file anywhere, either in a header or source file:

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

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

Now I understand that the use of the guard in header files is to prevent multiple inclusion of an already defined header file, it's common and well documented. If the macro is already defined, the entire header file is seen as 'blank' by the compiler and the double inclusion is prevented. Simple enough.

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

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?).

我不理解的问题是在#ifndef __HEADER_A_HPP__和#endif周围使用#include“header_a.hpp”。我的同事告诉我,这给夹杂物增加了第二层保护,但我不明白,如果第一层完全起作用(或者起作用?)

The only benefit I can come up with is that it outright stops the linker from bothering to find the file. Is this meant to improve compilation time (which was not mentioned as a benefit), or is there something else at work here that I am not seeing?

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

5 个解决方案

#1


103  

I am pretty sure that it is a bad practice to add another include guard like:

我很肯定,增加另一个包括后卫的做法是不好的,比如:

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

Here are some reasons why:

以下是一些原因:

  1. To avoid double inclusion it is enough to add a usual include guard inside the header file itself. It does the job well. Another include guard in the place of inclusion just messes the code and reduces readability.

    为了避免双重包含,可以在头文件本身中添加一个通常包含的保护。它做得很好。另一个代替包含的include保护只会使代码混乱,降低可读性。

  2. It adds unnecessary dependencies. If you change include guard inside the header file you have to change it in all places where the header is included.

    它增加了不必要的依赖关系。如果在头文件中更改include,则必须在头文件包含的所有地方更改它。

  3. It is definitely not the most expensive operation comparing the whole compilation/linkage process so it can hardly reduce the total build time.

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

  4. Any compiler worth anything already optimizes file-wide include-guards.

    任何有价值的编译器都已经优化了文件范围内的包-保护。

#2


47  

The reason for putting include guards in the header file is to prevent the contents of the header from being pulled into a translation unit more than once. That's normal, long-established practice.

将include警卫放入头文件的原因是为了防止头文件的内容被多次拖放到翻译单元中。这是正常的,历史悠久的实践。

The reason for putting redundant include guards in a source file is to avoid having to open the header file that's being included, and back in the olden days that could significantly speed up compilation. These days, opening a file is much faster than it used to be; further, compilers are pretty smart about remembering which files they've already seen, and they understand the include guard idiom, so can figure out on their own that they don't need to open the file again. That's a bit of hand-waving, but the bottom line is that this extra layer isn't needed any more.

在源文件中放置冗余包含保护的原因是为了避免必须打开包含在其中的头文件,并且回到以前可以显著加快编译的时代。现在,打开一个文件要比以前快得多;此外,编译器非常聪明地记住他们已经看到的文件,并且他们理解include保护习惯,因此可以自己确定不需要再次打开文件。这有点手忙脚乱,但底线是这个额外的层不再需要了。

EDIT: another factor here is that compiling C++ is far more complicated than compiling C, so it takes far longer, making the time spent opening include files a smaller, less significant part of the time it takes to compile a translation unit.

编辑:这里的另一个因素是编译c++要比编译C要复杂得多,所以它花费的时间要长得多,使得花在打开一个翻译单元所需的时间上的时间要小一些。

#3


22  

The only benefit I can come up with is that it outright stops the linker from bothering to find the file.

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

The linker will not be affected in any way.

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

It could prevent the pre-processor from bothering to find the file, but if the guard is defined, that means that it has already found the file. I suspect that if the pre-process time is reduced at all, the effect would be quite minimal except in the most pathologically recursively included monstrosity.

它可以防止预处理器麻烦地查找文件,但如果定义了保护,则意味着它已经找到了该文件。我怀疑,如果预处理时间减少,效果将非常小,除非在最病态的递归地包含了怪物。

It has a downside that if the guard is ever changed (for example due to conflict with another guard), all the conditionals before the include directives must be changed in order for them to work. And if something else uses the previous guard, then the conditionals must be changed for the include directive itself to work correctly.

它的缺点是,如果警卫发生了变化(例如由于与另一个守卫的冲突),在包含指令之前的所有条件都必须更改,以便它们能够工作。如果其他东西使用了先前的保护,那么必须更改条件以便include指令本身能够正确工作。

P.S. __HEADER_A_HPP__ is a symbol that is reserved to the implementation, so it is not something that you may define. Use another name for the guard.

P.S. __HEADER_A_HPP__是一个为实现保留的符号,所以它不是您可以定义的东西。用另一个名字来称呼守卫。

#4


17  

Older compilers on more traditional (mainframe) platforms (we're talking mid-2000s here) did not used to have the optimisation described in other answers, and so it really did used to significantly slow down preprocessing time having to re-read header files that have already been included (bearing in mind in a big, monolithic, enterprise-y project you're going to be including a LOT of header files). As an example, I've seen data that indicates a 26-fold speedup for a file with 256 header files each including the same 256 header files on the VisualAge C++ 6 for AIX compiler (which dates from the mid-2000s). This is a rather extreme example but this sort of speed-up does add up.

旧的传统(大型机)平台上的编译器(我们这里说的2000年代中期)用于没有描述的优化其他答案,所以它真的用来显著减缓预处理时间不得不重读已经包含的头文件(考虑在一个大整体,企业项目你会包括大量的头文件)。例如,我看到的数据表明,对于一个包含256个头文件(每个头文件都包含AIX编译器VisualAge c++ 6上相同的256个头文件)的文件来说,加速速度是26倍(从2000年中期开始)。这是一个相当极端的例子,但这种加速确实增加了。

However, all modern compilers, even on mainframe platforms such as AIX and Solaris, perform enough optimisation for header inclusion that the difference these days really is negligible. Therefore there is no good reason to have these any more.

然而,所有现代编译器,甚至在AIX和Solaris等大型机平台上,都对头包含进行了足够的优化,以至于现在的差异实际上可以忽略不计。因此,没有理由再拥有这些了。

This does, however, explain why some companies still hang on to the practice, because relatively recently (at least in C/C++ codebase age terms) it was still worthwhile for very large monolithic projects.

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

#5


8  

Although there are people arguing against it, in practice '#pragma once' works perfectly and the main compilers (gcc/g++, vc++) support it.

虽然有人反对它,但在实践中,“#pragma曾经”是完美的,主编译器(gcc/g++, vc++)支持它。

So whatever puristic argumentation people are spreading, it works a lot better:

所以不管人们传播什么清教徒式的争论,它的效果都要好得多:

  1. Fast
  2. No maintenance, no trouble with mysterious non-inclusion because you copied an old flag
  3. 没有维护,没有神秘不包含的麻烦,因为您复制了一个旧的旗帜
  4. Single line with obvious meaning versus cryptic lines spread in file
  5. 单行具有明显的意义,而暗行则分散在文件中

So simply put:

所以简单地说:

#pragma once

at the start of the file, and that's it. Optimized, maintainable, and ready to go.

在文件的开头,就是这样。优化的,可维护的,准备好了。

#1


103  

I am pretty sure that it is a bad practice to add another include guard like:

我很肯定,增加另一个包括后卫的做法是不好的,比如:

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

Here are some reasons why:

以下是一些原因:

  1. To avoid double inclusion it is enough to add a usual include guard inside the header file itself. It does the job well. Another include guard in the place of inclusion just messes the code and reduces readability.

    为了避免双重包含,可以在头文件本身中添加一个通常包含的保护。它做得很好。另一个代替包含的include保护只会使代码混乱,降低可读性。

  2. It adds unnecessary dependencies. If you change include guard inside the header file you have to change it in all places where the header is included.

    它增加了不必要的依赖关系。如果在头文件中更改include,则必须在头文件包含的所有地方更改它。

  3. It is definitely not the most expensive operation comparing the whole compilation/linkage process so it can hardly reduce the total build time.

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

  4. Any compiler worth anything already optimizes file-wide include-guards.

    任何有价值的编译器都已经优化了文件范围内的包-保护。

#2


47  

The reason for putting include guards in the header file is to prevent the contents of the header from being pulled into a translation unit more than once. That's normal, long-established practice.

将include警卫放入头文件的原因是为了防止头文件的内容被多次拖放到翻译单元中。这是正常的,历史悠久的实践。

The reason for putting redundant include guards in a source file is to avoid having to open the header file that's being included, and back in the olden days that could significantly speed up compilation. These days, opening a file is much faster than it used to be; further, compilers are pretty smart about remembering which files they've already seen, and they understand the include guard idiom, so can figure out on their own that they don't need to open the file again. That's a bit of hand-waving, but the bottom line is that this extra layer isn't needed any more.

在源文件中放置冗余包含保护的原因是为了避免必须打开包含在其中的头文件,并且回到以前可以显著加快编译的时代。现在,打开一个文件要比以前快得多;此外,编译器非常聪明地记住他们已经看到的文件,并且他们理解include保护习惯,因此可以自己确定不需要再次打开文件。这有点手忙脚乱,但底线是这个额外的层不再需要了。

EDIT: another factor here is that compiling C++ is far more complicated than compiling C, so it takes far longer, making the time spent opening include files a smaller, less significant part of the time it takes to compile a translation unit.

编辑:这里的另一个因素是编译c++要比编译C要复杂得多,所以它花费的时间要长得多,使得花在打开一个翻译单元所需的时间上的时间要小一些。

#3


22  

The only benefit I can come up with is that it outright stops the linker from bothering to find the file.

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

The linker will not be affected in any way.

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

It could prevent the pre-processor from bothering to find the file, but if the guard is defined, that means that it has already found the file. I suspect that if the pre-process time is reduced at all, the effect would be quite minimal except in the most pathologically recursively included monstrosity.

它可以防止预处理器麻烦地查找文件,但如果定义了保护,则意味着它已经找到了该文件。我怀疑,如果预处理时间减少,效果将非常小,除非在最病态的递归地包含了怪物。

It has a downside that if the guard is ever changed (for example due to conflict with another guard), all the conditionals before the include directives must be changed in order for them to work. And if something else uses the previous guard, then the conditionals must be changed for the include directive itself to work correctly.

它的缺点是,如果警卫发生了变化(例如由于与另一个守卫的冲突),在包含指令之前的所有条件都必须更改,以便它们能够工作。如果其他东西使用了先前的保护,那么必须更改条件以便include指令本身能够正确工作。

P.S. __HEADER_A_HPP__ is a symbol that is reserved to the implementation, so it is not something that you may define. Use another name for the guard.

P.S. __HEADER_A_HPP__是一个为实现保留的符号,所以它不是您可以定义的东西。用另一个名字来称呼守卫。

#4


17  

Older compilers on more traditional (mainframe) platforms (we're talking mid-2000s here) did not used to have the optimisation described in other answers, and so it really did used to significantly slow down preprocessing time having to re-read header files that have already been included (bearing in mind in a big, monolithic, enterprise-y project you're going to be including a LOT of header files). As an example, I've seen data that indicates a 26-fold speedup for a file with 256 header files each including the same 256 header files on the VisualAge C++ 6 for AIX compiler (which dates from the mid-2000s). This is a rather extreme example but this sort of speed-up does add up.

旧的传统(大型机)平台上的编译器(我们这里说的2000年代中期)用于没有描述的优化其他答案,所以它真的用来显著减缓预处理时间不得不重读已经包含的头文件(考虑在一个大整体,企业项目你会包括大量的头文件)。例如,我看到的数据表明,对于一个包含256个头文件(每个头文件都包含AIX编译器VisualAge c++ 6上相同的256个头文件)的文件来说,加速速度是26倍(从2000年中期开始)。这是一个相当极端的例子,但这种加速确实增加了。

However, all modern compilers, even on mainframe platforms such as AIX and Solaris, perform enough optimisation for header inclusion that the difference these days really is negligible. Therefore there is no good reason to have these any more.

然而,所有现代编译器,甚至在AIX和Solaris等大型机平台上,都对头包含进行了足够的优化,以至于现在的差异实际上可以忽略不计。因此,没有理由再拥有这些了。

This does, however, explain why some companies still hang on to the practice, because relatively recently (at least in C/C++ codebase age terms) it was still worthwhile for very large monolithic projects.

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

#5


8  

Although there are people arguing against it, in practice '#pragma once' works perfectly and the main compilers (gcc/g++, vc++) support it.

虽然有人反对它,但在实践中,“#pragma曾经”是完美的,主编译器(gcc/g++, vc++)支持它。

So whatever puristic argumentation people are spreading, it works a lot better:

所以不管人们传播什么清教徒式的争论,它的效果都要好得多:

  1. Fast
  2. No maintenance, no trouble with mysterious non-inclusion because you copied an old flag
  3. 没有维护,没有神秘不包含的麻烦,因为您复制了一个旧的旗帜
  4. Single line with obvious meaning versus cryptic lines spread in file
  5. 单行具有明显的意义,而暗行则分散在文件中

So simply put:

所以简单地说:

#pragma once

at the start of the file, and that's it. Optimized, maintainable, and ready to go.

在文件的开头,就是这样。优化的,可维护的,准备好了。