[2026] C++ Header Files (.h/.hpp): Declarations, Include Guards, and What Belongs Where
이 글의 핵심
How C++ headers declare APIs while .cpp files define behavior: ODR-safe patterns, include guards, forward declarations, templates, inline functions, and a small logger example.
Introduction
C++ headers (.h, .hpp) carry declarations that describe APIs; source files provide definitions. Headers are how you modularize code and share types across translation units.
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.h - Header (declaration)
#ifndef MATH_H
#define MATH_H
int add(int a, int b); // Declaration
#endif
// math.cpp - Source (definition)
#include "math.h"
int add(int a, int b) { // Definition
return a + b;
}
1. Declaration vs Definition
| Declaration | Definition | |
|---|---|---|
| Role | Introduces a name | Provides the actual implementation |
| Duplication | Allowed (multiple times) | One per program (ODR) |
| Example | int add(int, int); | int add(int a, int b) { return a+b; } |
| Location | Usually in headers | Usually in .cpp files |
One Definition Rule (ODR)
ODR: Each definition must be unique across the whole program, with specific exceptions:
- Inline functions
- Templates
- constexpr variables (C++17+)
- Class definitions (must be identical in all TUs)
2. Include Guards
Traditional include guards
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
class Widget {
int value_;
public:
Widget(int v);
int getValue() const;
};
#endif // WIDGET_H
#pragma once
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// widget.h
#pragma once
class Widget {
int value_;
public:
Widget(int v);
int getValue() const;
};
Comparison:
| Feature | #ifndef guards | #pragma once |
|---|---|---|
| Standard | Yes (C++98+) | No (widely supported) |
| Portability | 100% | ~99% (all major compilers) |
| Simplicity | Verbose | Concise |
| Speed | Slightly slower | Slightly faster |
3. What belongs in headers
✅ Safe in headers
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// declarations.h
#pragma once
// 1. Forward declarations
class Window;
// 2. Type aliases
using IntVector = std::vector<int>;
// 3. Enumerations
enum class Color { Red, Green, Blue };
// 4. Class declarations
class Widget {
int value_;
public:
Widget(int v) : value_(v) {} // Inline definition OK
int getValue() const; // Declaration only
};
// 5. Inline functions
inline int square(int x) {
return x * x;
}
// 6. constexpr functions
constexpr int cube(int x) {
return x * x * x;
}
// 7. Templates (full definition required)
template<typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& item) { data_.push_back(item); }
T pop() { T val = data_.back(); data_.pop_back(); return val; }
};
// 8. Inline variables (C++17+)
inline int globalCounter = 0;
// 9. constexpr variables
constexpr double PI = 3.14159265359;
// 10. extern declarations
extern int externalVariable;
❌ Avoid in headers
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// bad_header.h
// ❌ Non-inline function definitions
int add(int a, int b) { // ODR violation if included in multiple .cpp files
return a + b;
}
// ❌ Non-inline global variables
int globalVar = 42; // Multiple definitions!
// ❌ using namespace in headers
using namespace std; // Pollutes all includers
// ❌ Implementation details
static int helperFunction() { // Each TU gets its own copy
return 42;
}
4. Complete example: Calculator
calculator.h
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#pragma once
class Calculator {
public:
// Inline member functions
int add(int a, int b) const {
return a + b;
}
// Declarations only
int multiply(int a, int b) const;
double divide(double a, double b) const;
// Static utility
static int square(int x);
};
// Free function declaration
int factorial(int n);
calculator.cpp
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include "calculator.h"
#include <stdexcept>
int Calculator::multiply(int a, int b) const {
return a * b;
}
double Calculator::divide(double a, double b) const {
if (b == 0.0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
int Calculator::square(int x) {
return x * x;
}
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
main.cpp
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include "calculator.h"
#include <iostream>
int main() {
Calculator calc;
std::cout << "5 + 3 = " << calc.add(5, 3) << "\n";
std::cout << "5 * 3 = " << calc.multiply(5, 3) << "\n";
std::cout << "10 / 2 = " << calc.divide(10, 2) << "\n";
std::cout << "4² = " << Calculator::square(4) << "\n";
std::cout << "5! = " << factorial(5) << "\n";
}
5. Template example
Templates must have full definitions visible in headers:
stack.h
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#pragma once
#include <vector>
#include <stdexcept>
template<typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& item) {
data_.push_back(item);
}
T pop() {
if (data_.empty()) {
throw std::runtime_error("Stack is empty");
}
T value = data_.back();
data_.pop_back();
return value;
}
bool empty() const {
return data_.empty();
}
size_t size() const {
return data_.size();
}
};
Usage
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include "stack.h"
#include <iostream>
int main() {
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
std::cout << intStack.pop() << "\n"; // 20
std::cout << intStack.pop() << "\n"; // 10
}
6. Forward declarations
Break circular dependencies and reduce compile time:
window.h
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#pragma once
// Forward declaration instead of #include "widget.h"
class Widget;
class Window {
Widget* widget_; // Pointer OK with forward declaration
public:
Window();
~Window();
void setWidget(Widget* w);
Widget* getWidget() const;
};
window.cpp
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include "window.h"
#include "widget.h" // Full definition needed here
Window::Window() : widget_(nullptr) {}
Window::~Window() {
// Can use Widget here because we included the full definition
}
void Window::setWidget(Widget* w) {
widget_ = w;
}
Widget* Window::getWidget() const {
return widget_;
}
When forward declaration works:
- Pointers or references to the type
- Function parameters/return types (declaration only) When full definition needed:
- Creating objects
- Accessing members
- Using sizeof
- Inheritance
7. Common problems and solutions
Problem 1: Multiple definition error
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ bad.h
int globalVar = 42; // Defined in header!
// Every .cpp that includes this gets a copy
// Linker error: multiple definition of 'globalVar'
Solution: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ good.h
extern int globalVar; // Declaration
// good.cpp
int globalVar = 42; // Definition (once)
// Or C++17 inline variable
inline int globalVar = 42; // OK in header
Problem 2: Circular includes
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// a.h
#include "b.h"
class A {
B* b_;
};
// b.h
#include "a.h"
class B {
A* a_;
};
// Circular dependency!
Solution: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// a.h
#pragma once
class B; // Forward declaration
class A {
B* b_;
};
// b.h
#pragma once
class A; // Forward declaration
class B {
A* a_;
};
Problem 3: Include bloat
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Slow compilation
// widget.h
#include <vector>
#include <string>
#include <map>
#include <algorithm>
// ....20 more headers
class Widget {
int value_; // Only uses int!
};
Solution: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Minimal headers
// widget.h
#pragma once
class Widget {
int value_;
public:
Widget(int v);
int getValue() const;
};
// widget.cpp - Heavy includes here
#include "widget.h"
#include <vector>
#include <string>
// ....other headers
8. Best practices
1. Self-contained headers
Every header should compile on its own: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// widget.h
#pragma once
#include <string> // Don't rely on includers to provide this
class Widget {
std::string name_; // Uses std::string
public:
Widget(const std::string& name);
};
Test: Put your header first in the .cpp:
#include "widget.h" // If this fails, header isn't self-contained
#include <iostream>
// ....other includes
2. Include order
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
// widget.cpp
#include "widget.h" // 1. Own header first
#include <vector> // 2. C++ standard library
#include <string>
#include "util.h" // 3. Project headers
#include "helper.h"
3. Minimize dependencies
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Heavy header
#include <vector>
#include <map>
class Widget {
std::vector<int> data_; // Exposes std::vector in header
};
// ✅ Lighter with pimpl
class Widget {
struct Impl;
std::unique_ptr<Impl> pimpl_;
public:
Widget();
~Widget();
};
9. Performance impact
Benchmark (1000 file project):
| Technique | Compile time | Improvement |
|---|---|---|
| Baseline (heavy headers) | 180s | - |
| Forward declarations | 145s | 19% faster |
| Minimal includes | 120s | 33% faster |
| Precompiled headers | 45s | 75% faster |
10. Modern alternatives
C++20 Modules
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// math.ixx (module interface)
export module math;
export int add(int a, int b) {
return a + b;
}
// main.cpp
import math;
int main() {
int result = add(5, 3);
}
Benefits:
- No include guards needed
- Faster compilation
- Better encapsulation
- Order-independent
Summary
- Headers: Declarations (+ allowed definitions like templates, inline)
- Guards: Use
#pragma onceor#ifndefto prevent double inclusion - Forward declarations: Reduce dependencies and compile time
- Templates: Full definitions must be in headers
- Inline functions: Can live in headers without ODR violations
- Minimize includes: Keep headers lightweight
Related posts
Keywords
C++, header files, include guards, pragma once, forward declaration, ODR, templates, compilation, modular programming