The use of double include guards in C++

C++MacrosLinker

C++ Problem Overview


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":

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

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?

C++ Solutions


Solution 1 - C++

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.

  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.

  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.

Solution 2 - C++

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.

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.

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.

Solution 3 - C++

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

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.

Solution 4 - C++

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.

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.

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.

Solution 5 - C++

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

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. Single line with obvious meaning versus cryptic lines spread in file

So simply put:

#pragma once

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionsh3rifmeView Question on Stackoverflow
Solution 1 - C++Edgar RokjānView Answer on Stackoverflow
Solution 2 - C++Pete BeckerView Answer on Stackoverflow
Solution 3 - C++eerorikaView Answer on Stackoverflow
Solution 4 - C++MuzerView Answer on Stackoverflow
Solution 5 - C++Bert BrilView Answer on Stackoverflow