C++ Header Guards: #ifndef vs #pragma once and Portability
Key takeaways
A header guard makes the preprocessor skip a header's contents the second time it is included in the same translation unit. #ifndef/#define/#endif is standard and portable; #pragma once is shorter and supported by all major compilers. Neither prevents multiple-definition link errors across .cpp files, and a copy-pasted guard macro silently hides a whole header.
The error guards exist to prevent
C++ has no real notion of importing a file. #include pastes the file’s text into the current source file before compilation, and the result (one .cpp plus everything it pulled in) is a translation unit. If the same header gets pasted into one translation unit twice, everything in it is defined twice.
That happens more easily than it sounds. Suppose shape.h includes point.h, and main.cpp includes both:
// point.h (no guard)
struct Point { int x, y; };
// shape.h
#include "point.h"
struct Shape { Point origin; };
// main.cpp
#include "point.h"
#include "shape.h" // pulls in point.h a second time
int main() { Shape s{}; return s.origin.x; }
GCC stops with:
point.h:1:8: error: redefinition of 'struct Point'
point.h:1:8: note: previous definition of 'struct Point'
Nobody wrote Point twice, and main.cpp is not wrong for including a header it uses directly. The fix belongs in point.h: it has to be safe to include any number of times. That property is what a header guard provides.
To see how a header ended up included twice, g++ -H (Clang supports it too) prints the include tree, one dot per nesting level:
. point.h
. shape.h
.. point.h
#ifndef guards
#ifndef MYPROJECT_GEOMETRY_POINT_H
#define MYPROJECT_GEOMETRY_POINT_H
struct Point { int x, y; };
#endif // MYPROJECT_GEOMETRY_POINT_H
The first time the preprocessor reads the file, the macro is not defined, so it defines it and processes the body. The second time, #ifndef is false and everything up to the matching #endif is skipped. This uses nothing but standard preprocessor directives, so it works with every C and C++ compiler ever made.
A common worry is that the compiler still has to open and scan the file the second time. GCC’s preprocessor documents an optimization for exactly this idiom: when the whole file is wrapped in an #ifndef/#endif pair with nothing outside it except comments, it remembers the controlling macro and does not reread the file if the macro is still defined. Other major compilers do something similar. So the speed argument between guards and #pragma once is, in practice, not worth weighing; the number of headers you include matters far more.
Naming the macro
The guard macro lives in the same global namespace as every other macro in the program, so it has to be unique across the project and all its dependencies. Generic names are where things go wrong. Here are two unrelated headers that both use the obvious name:
// net/utils.h
#ifndef UTILS_H
#define UTILS_H
inline int netPort() { return 80; }
#endif
// str/utils.h
#ifndef UTILS_H
#define UTILS_H
inline int trimLen() { return 0; }
#endif
// main.cpp
#include "net/utils.h"
#include "str/utils.h" // UTILS_H already defined: whole file skipped
int main() { return netPort() + trimLen(); }
main.cpp:3:33: error: 'trimLen' was not declared in this scope
The error points at the use, not at the guard, and the header visibly declares trimLen, which is why this one can take a while to spot. The same thing happens when a new header is created by copying an old one and the guard is not renamed; it compiles until the day both are included together.
Base the name on the project and the file’s path, for example MYPROJECT_NET_UTILS_H (the Google C++ style guide uses the form <PROJECT>_<PATH>_<FILE>_H_). Avoid names that start with an underscore followed by an uppercase letter, or that contain a double underscore, such as _UTILS_H or __UTILS_H__. Those identifiers are reserved for the implementation, and using them risks colliding with the standard library’s own guards.
Clang also warns when the #define does not match the #ifndef it follows (a typo like #ifndef POINT_H / #define PIONT_H), which would otherwise make the guard useless: 'POINT_H' is used as a header guard here, followed by #define of a different macro. clang-tidy’s llvm-header-guard check can enforce path-based names across a code base.
#pragma once
#pragma once
struct Point { int x, y; };
Instead of a macro, the compiler records the file itself and refuses to process it again within the translation unit. There is no name to choose, so there is nothing to collide or to forget to rename. GCC, Clang, MSVC and the Intel compilers all support it.
The catch is in the phrase “the file itself”. The standard does not define #pragma once, and each compiler decides for itself when two include paths refer to the same file. The case that bites in practice is two copies of the same header reachable through different paths: a vendored copy of a library in the source tree and an installed copy under /usr/include, or the same directory mounted twice in a build container. To #pragma once those are different files, so both get processed and you are back to redefinition of .... With #ifndef, both copies use the same macro and the second is skipped, which hides the duplication instead of reporting it but at least compiles. Symlinks and network file systems are the other area where compilers have historically disagreed about file identity.
| Situation | Reasonable choice |
|---|---|
| Application code built with GCC, Clang or MSVC | #pragma once |
| Library distributed as source to unknown toolchains | #ifndef |
| Headers that may exist as multiple copies on the include path | #ifndef (and fix the include path) |
| Existing code base | Whatever it already uses, consistently |
Some projects put both in every header. It is harmless, but it also brings back the macro-naming problem that #pragma once was supposed to remove.
What guards do not protect against
This is the misunderstanding I run into most with header guards: the belief that a guarded header can contain anything. A guard works inside one translation unit. Each .cpp file is compiled separately, starts with no macros defined, and processes the header fresh.
// util.h
#ifndef UTIL_H
#define UTIL_H
int helper() { return 42; } // a definition, not inline
#endif
// a.cpp
#include "util.h"
int a() { return helper(); }
// b.cpp
#include "util.h"
int a();
int main() { return helper() + a(); }
Both files compile. The link fails:
multiple definition of `helper()'; ...a.cpp:(.text+0x0): first defined here
The guard did its job in each translation unit; the problem is that a.o and b.o each contain a definition of helper, which violates the One Definition Rule. The fixes are to mark the function inline (which allows identical definitions in several translation units), make it a template, or leave only the declaration int helper(); in the header and move the body to util.cpp. The same applies to non-const global variables in headers; since C++17, inline int counter = 0; is the header-safe form.
Guards and circular includes
Guards also change what a circular include looks like. Without them, a.h including b.h including a.h would recurse until the compiler gives up on include depth. With guards, the recursion stops, but one of the headers ends up seeing the other before its contents have been processed:
// a.h
#ifndef A_H
#define A_H
#include "b.h"
struct A { B* b; };
#endif
// b.h
#ifndef B_H
#define B_H
#include "a.h" // A_H already defined: skipped, A not declared yet
struct B { A a; };
#endif
Including a.h from a .cpp file gives:
b.h:4:12: error: 'A' does not name a type
The message is confusing precisely because a.h is included right above. The second time around, the guard skipped it, and struct A had not been reached yet. The real fix is to break the cycle. A only needs a pointer to B, so a.h can replace #include "b.h" with a forward declaration struct B;, and the cycle disappears. When both sides genuinely need the full definition of the other by value, the design itself is circular and one of the types has to hold the other through a pointer or reference.
Where modules fit
C++20 modules replace textual inclusion for code that uses them: an import does not paste text, macros defined in a module do not leak out, and importing twice is naturally harmless, so module interface units do not need guards. Ordinary headers, including those you still #include from inside a module’s global module fragment, still need them. For most code bases, guards will be around for a long time.
Related Articles
- C++ Header Files : Declarations, Include Guards, and What
- C++ Forward Declaration: Reduce Includes and Break Cycles
- C++ Preprocessor Directives: #include, #define, and #ifdef
- C++ Include Paths: #include ’…’ vs <…>, -I, and CMake
- C++ Include Errors: Fixing “No such file or directory”
- Fixing “multiple definition” Linker Errors in C++: ODR, Header Definitions and inline
- C++20 Modules: export/import, Partitions and Building with GCC, Clang, MSVC and CMake