Include Guards
Finally, I’ll get an answer to my question! What is an include guard?!
We need to prevent multiple declarations if a header file happens to be included multiple times.
You might think you need something like, #include-unless-already-included "constants.hpp"
, but the designers of C realized they didn’t need a new kind of #include
, because they could use some other cool features of the preprocessor to do everything they needed.
This coding idiom is known as an include guard.
It’s probably best to just show you the include-guard idiom and then explain it. Here is constants.hpp
again with an include guard:
#ifndef CONSTANTS_HPP_INCLUDED
#define CONSTANTS_HPP_INCLUDED
constexpr double PI = 3.14159265358979;
#endif
What is this arcane and primitive incantation??
It is part of our heritage.
Yes, it does look a bit convoluted.
And it’s okay that it doesn’t make sense yet. It contains preprocessor directives that we haven’t mentioned and even if you do know about them, it’s some pretty strange logic.
New Preprocessor Directives
Preprocessor Macros
#define
MACRO_NAME
replacement text
creates a text-substitution macro for the preprocessor.
It’s basically a search-and-replace instruction—wherever MACRO_NAME
appears in a file (as a distinct word, not inside a string), it will be replaced with replacement text
before compilation.
Why do we SHOUT
MACRO_NAME
(in all caps)?It is a common convention to name preprocessor macros in all caps. That way they are distinguished from our regular variables!
But we use all caps for constants, too.
That’s because originally people also used preprocessor macros for constants (e.g.,
#define PI 3.14159265358979
), and C programmers still do. But in C++, we now useconstexpr
.Just to make sure I’m clear, with that preprocessor
define
, if we saydegrees = 2 * PI * radians;
the preprocessor changes the code so the compiler itself sees
degrees = 2 * 3.14159265358979 * radians;
Yes, simple macros are just like like a search and replace.
Returning to our include-guard example, we had
#define CONSTANTS_HPP_INCLUDED
which specifies that replacement text is nothing at all. Technically that means “whenever you see CONSTANTS_HPP_INCLUDED
in the program code, replace it with nothing”.
In our example it doesn't really matter what we replace that string with, because we’re not actually using it for textual search-and-replace at all, and we won’t ever be writing this weird word in our code.
We are just going use the fact that it was defined at all. And when we read the include file for the first time, it hasn’t been defined yet.
Conditional Code
The C preprocessor also provides a mechanism to prevent the compiler ever seeing some of the code in a file. The #if
, #ifdef
, and #ifndef
directives control this feature, known as conditional compilation.
When we say
#ifndef CONSTANTS_HPP_INCLUDED
...stuff...
#endif`
- If the condition is true, then
...stuff...
will be processed by the preprocessor (and thus eventually seen by the compiler). - If the condition is false, then
...stuff...
will be skipped by the preprocessor (and thus never seen by the compiler, as if it wasn’t there).
In this case the condition is “if not defined.”
So the text between
#ifndef
and#endif
is retained ifCONSTANTS_HPP_INCLUDED
is not defined and removed ifCONSTANTS_HPP_INCLUDED
has been defined?You got it!
Include Guard Logic
Okay. That makes sense, I guess. But I still don’t see why it prevents multiple declarations.
It’s probably easiest if we just walk through the logic step-by-step. Check out the video!
I’m sure I’ve got it, so I’m gonna skip the video.
You do you.
Include Guard Takeaways
The main things to know about include guards are
- In our header files, Include guards look like
#ifndef SOMEHEADER_HPP_INCLUDED
#define SOMEHEADER_HPP_INCLUDED
// Actual content of the file
#endif
- Include guards prevent multiple definitions when a header file is included twice.
- Every header file should have an include guard.
- In CS 70, for a header file
filename.hpp
, name the macroFILENAME_HPP_INCLUDED
.- Note that a common error is to make a new header file based on an existing file and forget to change the name in the include guard macro!
Is that all I need? What about all the logic of how it works?
It helps to understand the logic, but it’s also okay to just memorize the pattern so you can apply it to your own header files.
Do I have to name the macro
WHATEVER_HPP_INCLUDED
?Technically, you can name it whatever you want! The macro just needs to be undefined the first time the file is included, and a word that won’t be used elsewhere in your code (since it will be replaced with nothing if it appears).
So the macro name for each include file needs to be unique.
Yes. It would be bad if a macro of the same name were defined in some other file, preventing the contents of our header file from being included at all!
That’s why we base the macro name on the filename.
(When logged in, completion status appears here.)