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

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

이 글의 핵심

C++ 헤더 온리 라이브러리에 대한 실전 가이드입니다.

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

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

C++에서 헤더에 함수를 정의하면 multiple definition 링커 에러가 발생합니다. 하지만 inline, template, constexpr을 사용하면 헤더 온리 라이브러리를 만들 수 있습니다.

// ❌ 헤더에 일반 함수 정의
// 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 파일 없이 헤더 파일만으로 구성된 라이브러리입니다.

// 일반 라이브러리
// 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 키워드

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

사용:

// main.cpp
#include "utils.h"

int main() {
    print("Hello");
    std::cout << add(1, 2) << '\n';
}

장점:

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

3. template

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

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

사용:

#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 함수

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

사용:

#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. 컴파일러 최적화

// 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. 헤더 변경 시 전체 재컴파일

// mylib.h 수정
inline void foo() {
    // 변경
}

// mylib.h를 include하는 모든 파일 재컴파일

3. 바이너리 크기 증가

// inline 함수가 여러 번 인라인 전개
inline void bigFunction() {
    // 100줄 코드
}

// 10곳에서 호출 → 1000줄 코드

실전 예시

헤더 온리 JSON 라이브러리

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

사용:

#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 키워드를 사용하는가?
  • 헤더 가드가 있는가?
  • 컴파일 시간이 허용 가능한가?
  • 바이너리 크기가 허용 가능한가?

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

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

  • C++ 링커 에러 | multiple definition 해결
  • C++ inline 함수 | 인라인 최적화
  • C++ 템플릿 기초 | Template 가이드
  • C++ constexpr | 컴파일 타임 계산

마치며

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

핵심 원칙:

  1. inline 함수 사용
  2. template 활용
  3. 작은 라이브러리

inline, template, constexpr헤더 온리 라이브러리를 만들어보세요.

다음 단계: 헤더 온리 라이브러리를 이해했다면, C++ 템플릿 가이드에서 더 깊이 배워보세요.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |