[2026] C++ Header Files (.h/.hpp): Declarations, Include Guards, and What Belongs Where

[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

DeclarationDefinition
RoleIntroduces a nameProvides the actual implementation
DuplicationAllowed (multiple times)One per program (ODR)
Exampleint add(int, int);int add(int a, int b) { return a+b; }
LocationUsually in headersUsually 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
StandardYes (C++98+)No (widely supported)
Portability100%~99% (all major compilers)
SimplicityVerboseConcise
SpeedSlightly slowerSlightly 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):

TechniqueCompile timeImprovement
Baseline (heavy headers)180s-
Forward declarations145s19% faster
Minimal includes120s33% faster
Precompiled headers45s75% 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

  1. Headers: Declarations (+ allowed definitions like templates, inline)
  2. Guards: Use #pragma once or #ifndef to prevent double inclusion
  3. Forward declarations: Reduce dependencies and compile time
  4. Templates: Full definitions must be in headers
  5. Inline functions: Can live in headers without ODR violations
  6. Minimize includes: Keep headers lightweight

Keywords

C++, header files, include guards, pragma once, forward declaration, ODR, templates, compilation, modular programming

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3