Every C++ header file needs protection against multiple inclusion. Include the same header twice in a translation unit, and you'll get redefinition errors for classes, functions, and variables. Header guards solve this.
Two approaches dominate: the traditional #ifndef include guard and the preprocessor directive #pragma once. Both work. Both are widely supported. The question is which one fits your project.
The Traditional Approach: #ifndef Guards
The #ifndef pattern has been around since the beginning. It's verbose but explicit:
#ifndef MYHEADER_H
#define MYHEADER_H
// header content
#endif // MYHEADER_H
The preprocessor checks if MYHEADER_H is defined. If not, it defines it and processes the content. On subsequent inclusions, the macro is already defined, so the entire block is skipped.
This is standard C++. It works everywhere. The macro name is under your control, which means you're responsible for making it unique. Collisions are rare but possible—especially in large codebases where header names repeat across namespaces or modules.
Convention helps: uppercase filename with underscores, sometimes prefixed with the project name. MYPROJECT_UTILS_STRINGHELPER_H is harder to collide with than UTILS_H.
The Pragma Approach: #pragma once
Modern compilers support #pragma once, which does the same thing with less ceremony:
#pragma once
// header content
The compiler tracks which files have been included and skips them on subsequent inclusions. No macro name to manage. No risk of naming collisions. It's cleaner.
The tradeoff: #pragma once isn't part of the C++ standard. It's a widely supported extension. GCC, Clang, MSVC, and most other compilers have implemented it for years. If you're working on a project that might be compiled with an obscure or legacy compiler, this matters. For most projects, it doesn't.
Performance Considerations
In theory, #pragma once can be faster. The compiler identifies files by their filesystem path or inode, which can be quicker than processing the first few lines to check a macro.
In practice, modern compilers optimize #ifndef guards well. Once the preprocessor sees the pattern—an #ifndef at the top and a matching #endif at the bottom—it can skip the file entirely on subsequent inclusions without reading it. This optimization is sometimes called "include guard optimization."
Benchmark if you're curious, but compilation time differences are rarely noticeable unless you're dealing with massive projects and deep include hierarchies.
Symbolic Links and Edge Cases
One quirk of #pragma once: it relies on file identity. If the same header is accessible via different paths—through symbolic links, for example—the compiler might not recognize them as the same file. This can lead to multiple inclusions despite the pragma.
With #ifndef guards, the macro is the identity. The path doesn't matter. As long as the macro name is consistent, the guard works.
This edge case is uncommon. Most build systems don't create situations where headers are included via multiple filesystem paths. But if yours does, #ifndef is safer.
Mixing Both Approaches
Some projects use both:
#ifndef MYHEADER_H
#define MYHEADER_H
#pragma once
// header content
#endif
This provides the portability of #ifndef with the potential performance benefit of #pragma once. It's redundant, but redundancy in include guards isn't expensive.
What to Choose
For new projects on modern compilers, #pragma once is simpler. Less boilerplate, no macro naming, same outcome.
For projects that need maximum portability or interact with legacy systems, #ifndef guards are the safer default.
For large teams or codebases with strict standards, consistency matters more than the specific choice. Pick one and enforce it.
Related Reading
The C++ standard doesn't specify #pragma once, but it's widely documented in compiler references. GCC's preprocessor documentation covers pragma directives in detail.
For a deeper look at include guards and preprocessor optimization, see the C++ reference on #include.
If you're dealing with template-heavy code where compilation time actually matters, John Lakos's Large-Scale C++ Software Design discusses physical design and include dependencies at length. Older book, still relevant.
Header guards are fundamental to C++ compilation. Whether you write #pragma once or spell out the #ifndef, the goal is the same: include once, compile cleanly. If you find yourself thinking about header guards often enough that they feel like part of your workflow, our pragma once developer tee might resonate.
0 comments