본문으로 건너뛰기
Previous
Next
C++ 헤더 온리 라이브러리 | 'multiple definition' 에러 없이 만들기

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

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

이 글의 핵심

헤더에 구현을 두되 링커의 multiple definition 없이 배포하는 법, 그리고 인라인 링키지·ODR, 템플릿 인스턴스 제어, 컴파일 비용·ABI, 프로덕션에서 쓰는 패턴까지 확장합니다.

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

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

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

foo 함수의 구현 예제입니다.

// ❌ 헤더에 일반 함수 정의
// 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)인라인 링키지
  • 템플릿 인스턴스화 제어, 컴파일 시간, ABI 관점
  • 장단점, 프로덕션 패턴

실전 경험에서 배운 교훈

이 기술을 실무 프로젝트에 처음 도입했을 때, 공식 문서만으로는 알 수 없었던 많은 함정들이 있었습니다. 특히 프로덕션 환경에서 발생하는 엣지 케이스들은 로컬 개발 환경에서는 재현조차 되지 않았죠.

가장 어려웠던 점은 성능 최적화였습니다. 처음엔 “동작만 하면 되겠지”라고 생각했지만, 실제 사용자 트래픽이 몰리면서 병목 지점들이 하나씩 드러났습니다. 특히 데이터베이스 쿼리 최적화, 캐싱 전략, 에러 핸들링 구조 등은 여러 번의 장애를 겪으면서 개선해 나갔습니다.

이 글에서는 그런 시행착오를 통해 얻은 실전 노하우와, “이렇게 하면 안 된다”는 교훈들을 함께 정리했습니다. 특히 트러블슈팅 섹션은 실제 장애 대응 경험을 바탕으로 작성했으니, 비슷한 문제를 마주했을 때 참고하시면 도움이 될 것입니다.

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

정의

헤더 온리 라이브러리.cpp 파일 없이 헤더 파일만으로 구성된 라이브러리입니다.

add 함수의 구현 예제입니다.

// 일반 라이브러리
// 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 함수의 구현 예제입니다.

// 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[](size_t i) {
        return data_[i];
    }
};

#endif

사용:

main 함수의 구현 예제입니다.

#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

사용:

main 함수의 구현 예제입니다.

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

C/C++ 예제 코드입니다.

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

C/C++ 예제 코드입니다.

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

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

3. 바이너리 크기 증가

C/C++ 예제 코드입니다.

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

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

6. 인라인 링키지와 ODR

헤더 온리 설계의 이론적 축은 링커가 허용하는 중복 정의의 예외와, 표준이 요구하는 하나의 정의 규칙(ODR) 사이의 균형입니다. 단순히 inline을 붙였다고 끝이 아니라, 어떤 엔티티가 외부 링키지를 갖는지, 여러 번 정의되어도 되는지를 구분해야 합니다.

외부 링키지와 헤더 정의

일반적인 자유 함수를 헤더에 두면 각 번역 단위(translation unit)마다 동일한 심볼이 생성되고, 링커는 이를 multiple definition으로 거부합니다. inline으로 표시된 함수 정의는 여러 번 나타날 수 있으며, 표준은 이들이 토큰 단위로 동일(token-by-token identical)할 것을 요구합니다. 이는 같은 버전의 헤더를 모든 TU에서 포함한다는 전제 하에서, 링커가 중복을 하나의 구현으로 합치거나(mergeable), 동일하다고 가정하는 모델과 맞닿아 있습니다.

C++17부터는 inline 변수도 헤더에 둘 수 있어, 헤더 온리 라이브러리에서 전역 상수·싱글턴 포인터 같은 패턴을 깔끔하게 표현할 수 있습니다. const만 붙인 전역 객체의 내부 링키지 규칙(구버전 호환)과 혼동하지 말고, 헤더에 노출되는 정의inline과 ODR 요건을 함께 염두에 두는 것이 안전합니다.

ODR의 두 층

ODR은 프로그램 전체에서 하나의 정의라는 큰 원칙과, 인라인 함수·템플릿·특정 예외에 대한 세부 규칙으로 나뉩니다. 헤더 온리 코드에서는 다음이 특히 중요합니다.

  • 인라인 함수/변수: 여러 TU에 정의가 있어도 모두 동일해야 하며, 그렇지 않면 미정의 동작(UB)이 될 수 있습니다.
  • 클래스 정의: 클래스 본문은 보통 헤더에 한 번만 있지만, ODR 위반은 여러 헤더에서 미묘하게 다른 클래스 정의가 생길 때도 발생합니다(예: 매크로·#ifdef 분기).
  • 템플릿: 특수화·인스턴스화 규칙이 별도로 작동하며, 아래 템플릿 인스턴스화 제어에서 이어집니다.

실무에서는 같은 헤더가 다른 플래그로 두 번 컴파일되는 상황(일부 TU만 NDEBUG, 다른 매크로)이 ODR·ABI 문제를 일으키기 쉽습니다. 헤더 온리 라이브러리는 공개 API가 단일 정의를 유지하도록 빌드 매트릭스를 좁히는 것이 중요합니다.

static과 익명 네임스페이스

함수에 붙은 static(파일 범위) 또는 익명 네임스페이스내부 링키지를 부여해 TU마다 별도의 복사본이 생깁니다. 링커 에러는 나지 않지만, 헤더에 두면 TU마다 코드가 복제되어 바이너리 크기ICache 압력이 커집니다. “헤더 온리 유틸”에서 남용하면 팀이 체감하는 빌드·바이너리 비용으로 돌아옵니다.


7. 템플릿 인스턴스화 제어

템플릿은 정의가 헤더에 있어야 하는 경우가 많지만, “전부 헤더에 두면 인스턴스가 무한히 늘어난다”는 컴파일·링크 비용 문제와 직결됩니다.

암시적 인스턴스화

템플릿 함수나 클래스 멤버를 사용하는 TU마다 컴파일러는 필요한 특정 인스턴스를 생성합니다. 같은 인스턴스가 여러 .o에 생기면 링커는 하나로 합치기(duplicate elimination)를 합니다. 헤더 온리 순수 모델에서는 이 중복 생성이 컴파일 시간의 핵심 부담입니다.

명시적 인스턴스화와 extern template

명시적 인스턴화(explicit instantiation)한 번만 생성하도록 옮길 수 있습니다. 예를 들어 vector<int> 같은 자주 쓰는 인스턴스.cpp 한 곳에서 template class std::vector<int>;처럼 고정하면, 다른 TU에서는 extern template 선언으로 암시적 인스턴스화를 억제할 수 있습니다(컴파일러·표준 버전에 따라 세부 문법·지원 범위 확인).

이 패턴은 엄밀히는 “헤더 + 구현 분리” 하이브리드에 가깝습니다. 순수 헤더 온리를 유지하면서 비용을 줄이려면, 자주 쓰는 타입만 이렇게 분리하는 점진적 최적화가 현실적인 타협입니다.

헤더 온리에서의 실무 팁

  • 무거운 템플릿작은 non-template 래퍼로 감싸 include 범위를 줄입니다.
  • 특수화(specialization)는 한 헤더에 모아 정의가 한 곳에만 있게 하여 ODR 위험을 줄입니다.
  • 가능하면 constexpr/consteval컴파일 타임에 끝낼 부분을 분리하면, 런타임 인스턴스 수가 줄어듭니다.

8. 컴파일 시간 영향

헤더 온리는 배포가 쉽지만, 컴파일 단위당 작업량이 커지는 경향이 있습니다. 원인은 단순히 “줄 수”가 아니라 전처리·템플릿·constexpr 평가가 한 번에 몰리기 때문입니다.

왜 느려지는가

  • Include 전파: 한 헤더가 거대한 표준·서드파티 헤더를 끌어오면, 간접 의존성이 전부 매번 다시 파싱됩니다.
  • 템플릿 재분석: 같은 템플릿 정의가 수백 개의 TU에서 다시 분석·인스턴스화됩니다.
  • 최적화 경계: inline 전개가 많아지면 최적화기가 더 큰 함수 단위를 보게 되어, 컴파일 시간메모리 피크가 함께 오를 수 있습니다.

완화 전략

  • 포함 범위 최소화: 구현 세부가 필요 없는 TU에서는 전방 선언 가능한 타입으로 헤더 의존성을 끊습니다(템플릿이 아닌 경우).
  • PCH(미리 컴파일된 헤더) / C++20 모듈: 공통 베이스를 한 번만 처리하게 하여 반복 파싱을 줄입니다. 모듈 도입은 빌드 시스템 비용이 있으므로, 팀 상황에 맞게 선택합니다.
  • Unity build: 여러 .cpp를 합쳐 템플릿 인스턴스 중복을 줄이는 대신, 증분 빌드와 상충할 수 있어 CI·로컬 정책을 분리하기도 합니다.
  • 라이브러리 경계: 정말 무거운 부분은 정적 라이브러리로 빼고 안정적인 API 헤더만 얇게 유지하는 하이브리드가 종종 더 낫습니다.

9. ABI 안정성 관점

ABI(Application Binary Interface)컴파일된 오브젝트 간 호출 규약·레이아웃·이름 장식 규칙을 말합니다. 헤더 온리 라이브러리는 소스가 곧 배포물이라서, 바이너리 호환 문제가 다른 형태로 나타납니다.

구현이 헤더에 있을 때의 함의

  • 인라인·템플릿 본문을 바꾸면 해당 정의를 포함한 모든 TU가 재컴파일됩니다. 정적 .lib에서 같은 심볼을 링크하던 모델과 달리, 부분적 재빌드가 어렵습니다.
  • 서로 다른 컴파일 옵션으로 같은 헤더를 쓰면(예: 최적화 수준, RTTI, 예외), 동일 타입이라도 레이아웃·호출 규약이 어긋날 수 있어 미묘한 런타임 버그로 이어질 수 있습니다.
  • 표준 라이브러리 구현(libstdc++ vs libc++ 등)과 버전이 섞이면, 템플릿 인스턴스 내부 타입의 호환이 깨질 수 있습니다.

“헤더만 바꿨는데 왜 크래시?”

종종 인라인 함수이전 바이너리새 헤더가 섞이면, 한 TU는 새 구현, 다른 TU는 옛 구현을 링크한 것처럼 동작할 수 있습니다. 순수 헤더 온리에서는 전체 재빌드가 정답에 가깝고, 동적 라이브러리 경계에서는 버전 매크로·심볼 가시성으로 단일 정의를 강제하는 패턴이 필요합니다.


10. 프로덕션 헤더 온리 패턴

실제 제품 코드에서는 편의만큼이나 경계·버전·재현성이 중요합니다.

CMake INTERFACE 라이브러리

add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>
)
target_compile_features(mylib INTERFACE cxx_std_20)

소스가 없는 타겟으로 선언하면, 의존 프로젝트가 target_link_libraries 한 줄로 include 경로와 사용 요구사항을 전달받습니다. 헤더 온리에 특히 잘 맞습니다.

단일 헤더(single-include)와 detail 네임스페이스

  • 사용자는 mylib.hpp 하나만 include하고, 내부는 mylib/detail/*.hpp캡슐화합니다.
  • namespace mylib::detail실험적·불안정 API를 두어, semver에서 공개 API만 안정적으로 관리합니다.

버전·호환성 매크로

#define MYLIB_VERSION_MAJOR 1
#define MYLIB_VERSION_MINOR 4
#define MYLIB_VERSION_PATCH 2

조건부 API(#if MYLIB_VERSION >= 10400)를 문서화하고, 깨지는 변경메이저 버전과 함께 마이그레이션 가이드를 제공합니다.

하이브리드: 무거울 때만 .cpp

컴파일 비용이 임계치를 넘으면, 자주 쓰는 인스턴스만 명시적 인스턴스화하거나, non-template 구현.cpp로 옮기고 얇은 헤더만 공개합니다. “순수 헤더 온리”를 유지할지, 빌드 시간·바이너리를 얻을지는 팀의 SLA에 맞게 선택하면 됩니다.


실전 예시

헤더 온리 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[](const std::string& key) {
        if (!std::holds_alternative<std::map<std::string, Json>>(value_)) {
            value_ = std::map<std::string, Json>{};
        }
        auto& map = std::get<std::map<std::string, Json>>(value_);
        return map[key];
    }
};

#endif

사용:

main 함수의 구현 예제입니다.

#include "json.h"

int main() {
    Json obj;
    obj["name"] = "Alice";
    obj["age"] = 30;
}

정리

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

방법사용 시기
inline일반 함수·C++17 inline 변수
template제네릭 코드(인스턴스화 비용 감시)
constexpr컴파일 타임 계산
class 정의항상 가능(ODR·매크로 분기 주의)

핵심 규칙

  1. inline / 템플릿 / constexpr헤더 정의ODR을 동시에 만족시킨다.
  2. 인라인 링키지내부 링키지(static/익명 네임스페이스)의 차이를 알고, 후자의 코드 복제 비용을 관리한다.
  3. 템플릿 인스턴스가 폭증하면 명시적 인스턴스화·extern template·하이브리드를 검토한다.
  4. 컴파일 시간·바이너리·ABI순수 헤더 온리의 숨은 비용이다.

체크리스트

  • inline이 필요한 자유 함수·변수에 붙어 있는가?
  • 헤더 가드 또는 #pragma once이중 포함을 막는가?
  • 동일 매크로·플래그로 모든 TU가 같은 ODR 뷰를 보는가?
  • 템플릿·거대 헤더로 컴파일 시간이 한계를 넘지 않는가?
  • 바이너리 크기캐시 친화성이 허용 범위인가?
  • (필요 시) INTERFACE 타겟·단일 include·detail 네임스페이스공개 API를 관리하는가?

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

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


마치며

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

핵심 원칙:

  1. inline·ODR로 링커와 표준 규칙을 동시에 만족한다.
  2. 템플릿 인스턴스화include 그래프를 통제한다.
  3. 프로덕션 패턴(INTERFACE 타겟, detail, 버전 매크로)으로 경계를 명확히 한다.

inline, template, constexpr와 함께, 위 내부 원리를 염두에 두고 헤더 온리 라이브러리를 설계해 보세요.

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


관련 글

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「C++ 헤더 온리 라이브러리 | ‘multiple definition’ 에러 없이 만들기」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「C++ 헤더 온리 라이브러리 | ‘multiple definition’ 에러 없이 만들기」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


자주 묻는 질문 (FAQ)

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

A. 헤더 온리 라이브러리의 인라인·ODR, 템플릿 인스턴스화, 컴파일 시간·ABI, 프로덕션 패턴까지 실무 관점에서 정리합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

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

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

Q. 더 깊이 공부하려면?

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


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

C++, header-only, inline, template, ODR, 라이브러리, 링커, ABI, 컴파일 등으로 검색하시면 이 글이 도움이 됩니다.