본문으로 건너뛰기
Previous
Next
C++ Header Files : Declarations, Include Guards, and What

C++ Header Files : Declarations, Include Guards, and What

C++ Header Files : Declarations, Include Guards, and What

이 글의 핵심

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.

// 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

// widget.h
#ifndef WIDGET_H
#define WIDGET_H
class Widget {
    int value_;
public:
    Widget(int v);
    int getValue() const;
};
#endif  // WIDGET_H

#pragma once

// 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

// 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

// 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

#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

#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

#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

#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

#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

#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

#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

// ❌ bad.h
int globalVar = 42;  // Defined in header!
// Every .cpp that includes this gets a copy
// Linker error: multiple definition of 'globalVar'

Solution:

// ✅ 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

// a.h
#include "b.h"
class A {
    B* b_;
};
// b.h
#include "a.h"
class B {
    A* a_;
};
// Circular dependency!

Solution:

// 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

// ❌ Slow compilation
// widget.h
#include <vector>
#include <string>
#include <map>
#include <algorithm>
// ....20 more headers
class Widget {
    int value_;  // Only uses int!
};

Solution:

// ✅ 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:

// 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

// 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

// ❌ 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

// 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


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. How C++ headers declare APIs while .cpp files define behavior: ODR-safe patterns, include guards, forward declarations, … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, header, include, declaration, ODR 등으로 검색하시면 이 글이 도움이 됩니다.