1. 서론
클래스 템플릿을 이용하여 자료구조를 구현하고 있었는데 선언과 정의 코드를 헤더 파일에 전부 넣고 있었다. 그렇게 해야 오류가 나지 않는다는 단순한 이유로 그렇게 하고 있었는데 왜 이렇게 하는지에 대해 제대로 생각해본 적은 없던 것 같다. 일반적인 클래스를 구현할 때 처럼 선언과 정의를 분리하는 것이 정리된 것 같은 느낌이 들어서 더 좋은데 왜 굳이 이렇게 해야하는지 궁금해졌다. 이것이 최선인지에 대한 생각을 기록하려 한다.
2. 선언과 정의를 분리할 경우
#pragma once
template<typename T>
class TemplateTest
{
public:
TemplateTest(T data) : m_data(data)
{ }
T GetData();
private:
T m_data;
};
C++
복사
TemplateTest.h
#include "TemplateTest.h"
template<typename T>
T TemplateTest<T>::GetData()
{
return m_data;
}
C++
복사
TemplateTest.cpp
TemplateTest라는 클래스의 GetData 메서드의 선언과 정의를 헤더 파일과 소스 파일에 분리했다. main 함수에서 GetData 메서드를 호출하면 Visual Studio에선 LNK2019 링크 에러가 발생한다. LNK2019는 선언은 있는데 정의를 찾지 못했을 때 발생하는 링크 에러다. GetData의 정의가 존재하는데 왜 정의를 찾지 못했다고 나올까? 이유는 컴파일 과정에서 템플릿 정의를 발견했지만 컴파일하지 않았기 때문이다. 템플릿은 템플릿 파라미터에 인자가 주어져 인스턴스화될 경우에만 컴파일된다. TemplateTest.cpp에선 정의만 존재하고 인스턴스화하는 코드는 없기 때문에 정의는 컴파일되지 않아 정의와 관련된 어떤 코드도 생성되지 않는다. main에서 GetData를 호출할 때 컴파일 에러가 발생하지 않았다. GetData에 대한 선언은 헤더 파일에 존재하기 때문이다. 따라서 링크 과정에서 GetData의 정의를 찾을 수 있다고 간주할 것이다. 하지만 링크 과정에서 정의를 찾지 못해 링크 에러가 발생하게 된다.
3. 명시적 인스턴스화
#include "TemplateTest.h"
template<typename T>
T TemplateTest<T>::GetData()
{
return m_data;
}
template int TemplateTest<int>::GetData();
C++
복사
TemplateTest.cpp
아래 줄에 어떤 코드를 추가했다. 저 코드는 명시적으로 템플릿을 인스턴스화한 것이다. 템플릿 파라미터엔 int를 넘겨줬다. 이렇게 명시적으로 인스턴스화하면 GetData의 정의 코드가 컴파일돼 링크 에러없이 프로그램이 성공적으로 실행된다. 하지만 이 방법은 좋은 방법이 아닌 것 같다. 템플릿을 사용하는 가장 큰 이유는 모든 타입에 대해 하나의 코드로 일반화하는 것인데, 사용할 타입이 늘어나면 타입에 맞춰 명시적 인스턴스화 코드를 추가해야 한다. 코드를 누락하거나 타입을 햇갈리면 바로 링크 에러를 마주할 것이다. 이렇게 되면 템플릿을 쓰는 의미가 없다. 하지만 명시적 인스턴스화 자체가 나쁜 건 아닌 것 같다. 이것을 선언과 정의를 분리하는 용도가 아닌 템플릿을 강제로 컴파일시켜 내제된 오류를 찾아내는 용도로 쓸 수 있을 것 같다. 명시적으로 인스턴스화 하지 않으면 클래스 템플릿을 인스턴스화 하더라도 메서드 중 한번도 호출되지 않는 메서드는 컴파일되지 않는다. 호출되지 않는 메서드는 컴파일 오류가 있어도 컴파일을 거치지 않아 컴파일 에러가 발생하지 않는다. 이렇게 선택적으로 인스턴스화 하는 것을 선택적 인스턴스화라고 한다. 반면 명시적 인스턴스화는 클래스 템플릿의 모든 메서드를 컴파일하므로 메서드의 잠재된 컴파일 오류를 확인할 수 있다.
4. 최선의 방법
템플릿 클래스의 정의와 선언은 헤더 파일 안에 같이 넣는 게 맞는 것 같다. 아무리 생각해도 다른 방법들은 문제가 생길 여지가 많다. 어떤 글에선 템플릿 정의가 있는 cpp 파일을 include하는 방법도 소개했는데 추천하지 않는다고 했고 내가 생각해도 그건 정말 좋지 않은 방법같다. 프로그램 규모가 조금만 커져도 헤더 파일과 소스 파일 내용이 섞이면서 링크 관련 문제가 우수수 나올 것 같다.