[2026] C++ Header Guards: #ifndef vs #pragma once, Portability, and Modules
이 글의 핵심
Compare #ifndef/#define/#endif with #pragma once, fix redefinition errors, name guard macros safely, break circular includes with forward declarations, and peek at C++20 modules.
Introduction
You will see errors like:
error: redefinition of 'class MyClass'
Usually the same header was included twice in one translation unit. Header guards prevent duplicate processing of the same file. Note: Guards dedupe the same include path. Different paths to duplicate copies can still bite—normalize include paths in build settings. 아래 코드는 mermaid를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
A[Include header] --> B{Header guard present?}
B -->|No| C[Redefinition errors]
B -->|Yes| D{Already included?}
D -->|Yes| E[Skip contents]
D -->|No| F[Include contents]
F --> G[Define guard macro]
style C fill:#ff6b6b
style E fill:#51cf66
style F fill:#51cf66
What is a header guard?
A preprocessor technique that skips header contents on second inclusion within a TU.
#ifndef style (standard)
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
public:
MyClass(int v);
int getValue() const;
private:
int value;
};
#endif // MYCLASS_H
Pros: portable, explicit. Cons: boilerplate; macro name collisions if too generic.
#pragma once
#pragma once
class MyClass { /* ....*/ };
Pros: one line; no macro name. Cons: not ISO C++ (but supported by major compilers); edge cases with duplicate paths via symlinks.
How #ifndef works (conceptually)
First include: macro undefined → process body and define macro. Second include: macro defined → skip.
How #pragma once works (conceptually)
Compiler remembers file identity (path/inode) and skips reprocessing.
Practical patterns
- Basic header +
.cppsplit - Diamond includes: multiple headers include
point.h—guardedpoint.hprocesses once - Namespaces: guard
math_utils.happropriately - Templates: still need guards even though definitions are in headers
Naming conventions
Prefer project-unique macro names, e.g. MYPROJECT_SRC_FOO_BAR_BAZ_H_ (see Google style), not generic UTILS_H.
Choosing between #pragma once and #ifndef
| Situation | Common choice |
|---|---|
| Library (max portability) | #ifndef |
| App (modern toolchain) | #pragma once |
| Embedded/exotic compilers | #ifndef |
| Some teams use both defensively. |
Circular includes
Problem: a.h includes b.h includes a.h — sometimes incomplete types break.
Fixes:
- Forward declare when only pointers/references are needed
- Interface split (abstract base)
- Dependency inversion (shared abstraction)
Performance
Forward declarations and fewer includes reduce compile time more than micro-differences between guard styles.
PCH
Precompiled headers bundle stable includes (<vector>, <memory>, project common headers) to speed builds.
C++20 modules (contrast)
Modules avoid textual inclusion and macro leakage; migration is incremental.
Inline functions and templates
Even with guards, non-inline function definitions in headers can still cause ODR violations across TUs. inline functions and templates follow the usual ODR exceptions.
“Perfect header” template (sketch)
Include guard, system headers sorted, forward declarations, namespaces, class declarations, small inline helpers—end guard.
Checklists
- Every header has a guard
- Macro names are unique
#endifcomment matches the opening guard
FAQ (highlights)
Covers #pragma once vs #ifndef, redefinition symptoms, circular dependency strategies, naming, whether every header needs guards (yes for classic headers; modules differ), forward declaration limits, modules vs guards, compile-time impact, tooling (clang-tidy llvm-header-guard).
Quick decision (header authoring)
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
A[Write a header] --> B{Project type?}
B -->|New project| C{C++20 modules viable?}
B -->|Legacy| D{Existing style?}
B -->|Open-source library| E[Use #ifndef]
C -->|Yes| F[Consider modules]
C -->|No| G[#pragma once common]
D -->|#ifndef| E
D -->|#pragma once| G
D -->|Mixed| H[Stay consistent]
E --> I[PROJ_PATH_FILE_H]
G --> J[#pragma once]
F --> K[export module]
style F fill:#4dabf7
style G fill:#51cf66
style E fill:#ffd43b
Compile error triage
다음은 mermaid를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
A[Compile error] --> B{Error kind?}
B -->|redefinition| C[Check header guards]
B -->|does not name a type| D[Check circular includes]
B -->|undefined reference| E[Check definitions / link]
C --> C1{Guard present?}
C1 -->|No| C2[Add guard]
C1 -->|Yes| C3[Check macro name collisions]
D --> D1[Add forward declarations]
D1 --> D2[Prefer pointers / references]
E --> E1[Add definitions in .cpp]
E1 --> E2[Check linker inputs]
Related posts (internal links)
Practical tips
Debugging
- Warnings; include graph tools.
Performance
- Trim includes; consider PCH.
Code review
- Guard present? Unique name?
Practical checklist
Before coding
- Header self-contained?
While coding
- Guard present?
During review
- No duplicate definitions?