C++ 템플릿이 헤더 파일에 구현되어야 하는 이유
C++에서 템플릿(template)은 일반적인 클래스나 함수와는 다르게 동작합니다. 템플릿을 인스턴스화하면 컴파일러는 전달된 타입 인자를 기반으로 새로운 클래스를 직접 생성합니다. 즉, 템플릿 코드 안의 템플릿 매개변수 자리가 실제 사용된 타입으로 모두 치환된 클래스가 만들어지는 것입니다.
예를 들어 다음과 같은 템플릿 클래스가 있다고 가정해 보겠습니다.
template<typename T> class MyClass {
T foo;
T myMethod(T arg1, T arg2) {
// 구현부
}
};그리고 프로그램 어딘가에서 이 클래스를 다음과 같이 사용한다고 해봅시다.
MyClass<int> x;
컴파일러는 이 문장을 만나는 순간, 전달된 타입 인자(int)로 치환된 새로운 클래스를 생성합니다. 만약 서로 다른 템플릿 인자로 3개의 객체를 생성했다면, 컴파일러는 3개의 별도 클래스를 만들어냅니다. 이는 마치 아래처럼 각 타입별 클래스를 직접 작성한 것과 동일한 결과입니다.
class MyClassInt {
int foo;
int myMethod(int arg1, int arg2) {
// 구현부
}
};컴파일러가 구현 코드를 '미리' 봐야 하는 이유
핵심은 바로 여기에 있습니다. 컴파일러가 위와 같은 치환 작업을 수행하려면, MyClass<int> x; 같은 문장을 만나기 전에 템플릿 클래스와 메서드의 전체 구현에 접근할 수 있어야 합니다. 구현 코드가 없으면 어떤 타입이 들어오든 실제 코드를 생성할 수 없기 때문입니다.
그렇다면 템플릿 구현을 헤더 파일이 아닌 별도의 .cpp 파일에 작성하면 어떻게 될까요? .cpp 파일은 독립적인 번역 단위(translation unit)로 개별 컴파일되기 때문에, 다른 소스 파일에서는 그 내용을 참조할 수 없습니다. 결과적으로 컴파일러는 템플릿을 인스턴스화하는 시점에 구현 코드를 찾지 못하고, 최종적으로 링크 오류(linker error)가 발생하게 됩니다.
정리 및 대안 방법
이러한 이유로 C++ 템플릿의 선언과 구현은 일반적으로 헤더 파일(.h 또는 .hpp)에 함께 작성하는 것이 표준적인 관행입니다. 다만 몇 가지 예외적인 방법도 존재합니다.
- 명시적 인스턴스화(explicit instantiation): 사용할 타입을 미리 알고 있는 경우, .cpp 파일에서 특정 타입에 대해
template class MyClass<int>;형태로 명시적으로 인스턴스화할 수 있습니다. - .tpp / .ipp 파일 분리: 구현 코드를 별도 파일로 분리하되, 헤더 파일 마지막에서
#include로 포함시켜 컴파일러가 접근 가능하도록 유지하는 방식입니다.
결론적으로, C++ 템플릿은 컴파일 시점에 실제 타입별 코드를 생성해야 하므로, 컴파일러가 언제든 구현을 확인할 수 있도록 헤더 파일에 작성하는 것이 올바른 방법입니다.