[2026] C++ 헤더 온리 라이브러리 | multiple definition 에러 없이 만들기

[2026] C++ 헤더 온리 라이브러리 | multiple definition 에러 없이 만들기

이 글의 핵심

C++ 헤더 온리 라이브러리의 C++, 라이브러리, multiple, 들어가며: 헤더에 함수를 정의했더니 링커 에러가 나요를 실전 예제와 함께 상세히 설명합니다.

들어가며: “헤더에 함수를 정의했더니 링커 에러가 나요"

"헤더 온리 라이브러리는 어떻게 만드나요?”

C++에서 헤더에 함수를 정의하면 multiple definition 링커 에러가 발생합니다. 하지만 inline, template, constexpr을 사용하면 헤더 온리 라이브러리를 만들 수 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 헤더에 일반 함수 정의
// utils.h
void foo() {  // ❌ multiple definition
    std::cout << "foo\n";
}
// ✅ inline 사용
// utils.h
inline void foo() {  // ✅ OK
    std::cout << "foo\n";
}

이 글에서 다루는 것:

  • 헤더 온리 라이브러리란?
  • inline, template, constexpr
  • ODR (One Definition Rule)
  • 장단점과 사용 시기

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. 헤더 온리 라이브러리란?
  2. inline 함수
  3. template
  4. constexpr
  5. 장단점
  6. 정리

1. 헤더 온리 라이브러리란?

정의

헤더 온리 라이브러리.cpp 파일 없이 헤더 파일만으로 구성된 라이브러리입니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 일반 라이브러리
// math.h
int add(int a, int b);
// math.cpp
int add(int a, int b) {
    return a + b;
}
// 헤더 온리 라이브러리
// math.h
inline int add(int a, int b) {
    return a + b;
}
// math.cpp 없음!

유명한 헤더 온리 라이브러리

  • Eigen (선형대수)
  • nlohmann/json (JSON 파싱)
  • Catch2 (테스트 프레임워크)
  • fmt (포맷팅, 헤더 온리 모드 지원)

2. inline 함수

inline 키워드

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// utils.h
#ifndef UTILS_H
#define UTILS_H
#include <iostream>
inline void print(const std::string& msg) {
    std::cout << msg << '\n';
}
inline int add(int a, int b) {
    return a + b;
}
#endif

사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// main.cpp
#include "utils.h"
int main() {
    print("Hello");
    std::cout << add(1, 2) << '\n';
}

장점:

  • multiple definition 에러 없음
  • 컴파일러 최적화 가능

3. template

템플릿은 자동으로 헤더 온리

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.h
#ifndef MATH_H
#define MATH_H
template <typename T>
T add(T a, T b) {
    return a + b;
}
template <typename T>
class Vector {
    T* data_;
    size_t size_;
    
public:
    Vector(size_t size) : data_(new T[size]), size_(size) {}
    
    ~Vector() {
        delete[] data_;
    }
    
    T& operator {
        return data_[i];
    }
};
#endif

사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include "math.h"
int main() {
    std::cout << add(1, 2) << '\n';
    std::cout << add(1.5, 2.5) << '\n';
    
    Vector<int> vec(10);
    vec[0] = 42;
}

4. constexpr

constexpr 함수

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.h
#ifndef MATH_H
#define MATH_H
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int power(int base, int exp) {
    int result = 1;
    for (int i = 0; i < exp; ++i) {
        result *= base;
    }
    return result;
}
#endif

사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include "math.h"
int main() {
    constexpr int f5 = factorial(5);  // 컴파일 타임 계산
    std::cout << f5 << '\n';  // 120
    
    constexpr int p = power(2, 10);  // 1024
}

5. 장단점

장점

1. 사용 간편

// 헤더만 include
#include "mylib.h"
// 링크 설정 불필요

2. 컴파일러 최적화 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// inline 함수는 인라인 전개 가능
inline int add(int a, int b) {
    return a + b;
}
// 호출 오버헤드 없음
int x = add(1, 2);  // → int x = 3;

3. 빌드 설정 불필요

  • CMake 설정 간단
  • 라이브러리 빌드 불필요

단점

1. 컴파일 시간 증가

// 헤더를 include하는 모든 파일에서 컴파일
#include "biglib.h"  // 10000줄
// 100개 파일에서 include → 100번 컴파일

2. 헤더 변경 시 전체 재컴파일 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// mylib.h 수정
inline void foo() {
    // 변경
}
// mylib.h를 include하는 모든 파일 재컴파일

3. 바이너리 크기 증가 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// inline 함수가 여러 번 인라인 전개
inline void bigFunction() {
    // 100줄 코드
}
// 10곳에서 호출 → 1000줄 코드

실전 예시

헤더 온리 JSON 라이브러리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// json.h
#ifndef JSON_H
#define JSON_H
#include <string>
#include <map>
#include <vector>
#include <variant>
class Json {
    using Value = std::variant<
        std::nullptr_t,
        bool,
        int,
        double,
        std::string,
        std::vector<Json>,
        std::map<std::string, Json>
    >;
    
    Value value_;
    
public:
    Json() : value_(nullptr) {}
    Json(int v) : value_(v) {}
    Json(const std::string& v) : value_(v) {}
    
    template <typename T>
    T get() const {
        return std::get<T>(value_);
    }
    
    Json& operator {
        auto& map = std::get<std::map<std::string, Json>>(value_);
        return map[key];
    }
};
#endif

사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include "json.h"
int main() {
    Json obj;
    obj[name] = "Alice";
    obj[age] = 30;
}

정리

헤더 온리 라이브러리 만들기

방법사용 시기
inline일반 함수
template제네릭 코드
constexpr컴파일 타임 계산
class 정의항상 가능

핵심 규칙

  1. inline 함수 (multiple definition 방지)
  2. template (자동으로 헤더 온리)
  3. constexpr (컴파일 타임 계산)
  4. 작은 라이브러리 (컴파일 시간 고려)

체크리스트

  • inline 키워드를 사용하는가?
  • 헤더 가드가 있는가?
  • 컴파일 시간이 허용 가능한가?
  • 바이너리 크기가 허용 가능한가?

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

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


마치며

헤더 온리 라이브러리사용이 간편하지만, 컴파일 시간을 고려해야 합니다. 핵심 원칙:

  1. inline 함수 사용
  2. template 활용
  3. 작은 라이브러리 inline, template, constexpr헤더 온리 라이브러리를 만들어보세요. 다음 단계: 헤더 온리 라이브러리를 이해했다면, C++ 템플릿 가이드에서 더 깊이 배워보세요.

관련 글

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