[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. 헤더 온리 라이브러리란?
정의
헤더 온리 라이브러리는 .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 정의 | 항상 가능 |
핵심 규칙
- inline 함수 (multiple definition 방지)
- template (자동으로 헤더 온리)
- constexpr (컴파일 타임 계산)
- 작은 라이브러리 (컴파일 시간 고려)
체크리스트
- inline 키워드를 사용하는가?
- 헤더 가드가 있는가?
- 컴파일 시간이 허용 가능한가?
- 바이너리 크기가 허용 가능한가?
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 링커 에러 | multiple definition 해결
- C++ inline 함수 | 인라인 최적화
- C++ 템플릿 기초 | Template 가이드
- C++ constexpr | 컴파일 타임 계산
마치며
헤더 온리 라이브러리는 사용이 간편하지만, 컴파일 시간을 고려해야 합니다. 핵심 원칙:
- inline 함수 사용
- template 활용
- 작은 라이브러리 inline, template, constexpr로 헤더 온리 라이브러리를 만들어보세요. 다음 단계: 헤더 온리 라이브러리를 이해했다면, C++ 템플릿 가이드에서 더 깊이 배워보세요.