[C/C++] Template Function

그림자왕국·2020년 10월 26일
1

C++

목록 보기
5/24
post-thumbnail

Template Function

템플릿 함수는 호출이 되기 전까지 코드로만 존재하다가 호출이 되는 순간 그 타입에 맞춰서
알맞는 자료형의 템플릿 함수가 생성된다.

template<typename T> 
T foo(T a, T b) {}

템플릿 함수는 선언과 구현만 해놓고 따로 사용을 안할 경우 함수 자체가 실체화되지 않는다.
(코드 영역에 존재하지 않는다.)

직접 템플릿 함수를 호출할 때에야 비로서 실체화 된 함수가 코드영역에 생성된다는 뜻이다.
Template Instantiation(템플릿 인스턴스화)가 된다는 뜻

컴파일 타임까지 가서 템플릿을 호출한다는 걸 확인한 후에야 해당 자료형을 통한 함수가
코드영역에 생성되니 템플릿 함수를 외부 파일로 사용할 경우 조금 다른 빌드 방식을 가진다.

템플릿 함수가 호출된 후에야 <int.>에 맞는 add 함수가 생성되는 걸 확인할 수 있다.

즉 처음부터 템플릿 함수는 존재하지 않고 '틀'로서만 존재하며 실체화가 되어야 사용할 수 있다.


Template Function Linking Issue

위와 같이 템플릿 함수 파일들을 컴파일할 경우 링킹 에러가 발생하는데 이를 설명하자면

일단 템플릿 함수는 자료형이 정해져야지 실체화가 되는데 'foo.h' 헤더 파일에선 템플릿의
선언부만 존재하고 링킹할 'foo.cpp' 파일에는 정의가 써져있지만

foo.cpp 는 어떤 자료형을 가지고 바이너리 파일로 만들지 모르는 상태이기에 foo.cpp 파일의 템플릿 함수가 컴파일 타임에서 실체화 되지 않은 상태로 cpp 파일이 컴파일되어 심볼을 찾지 못하는 링킹 에러가 발생하는 것이다.

즉 템플릿의 자료형을 foo.cpp 파일이 알 수 없기에 바이너리로 만들 때 Template Instantiation(템플릿 인스턴스화)가 일어나지 않고 실체화 되지 않은 상태의 cpp 파일을 합치려하니 링킹 에러가 발생하는 것이다.

템플릿 함수가 실체화되려면 함수에 자료형(<>)을 설정하여 호출하거나 명시적으로 선언해줘야 하지만 foo.cpp는 main과 링킹 타임에서야 만나는 사이니 오브젝트 파일이 만들어지는 독자적인 컴파일 타임 땐 템플릿 함수가 호출되지 않은 걸로 인지해서 템플릿 함수가 인스턴스화되지 않는다.


Solution

이러한 링킹 문제를 해결하기 위해서 템플릿 함수를 사용할 때는 정의부를 포함한 모든 내용을
헤더 파일에 넣어주는게 일반적인 관례다.

또는 헤더파일에 넣기 싫으면 cpp 파일에 명시적 인스턴스화(explicit Instantiation)를 시켜주면 된다.

물론 명시적 인스턴스화를 사용한다면 새로운 자료형이 들어올 때마다 매번 cpp 파일을 수정해줘야 하는 번거로움은 있다.

template int foo<int>(int); // int type으로 명시적 인스턴스화

또는 걍 main에서 foo.cpp 파일을 include 해주면 되는데 매우 위험하니 넘어가도록 하자.

추가로 템플릿 틀을 함수 템플릿이라 부르고 템플릿 틀이 인스턴스화된게 템플릿 함수라고 불린다.


함수 템플릿 명시적 특수화 (Explicit Specialization)

함수 템플릿의 자료형을 직접 선정하는 명시적 특수화를 통해 특정 자료형의 템플릿 함수가 만들어질 경우 해당 자료형에 따른 특정한 동작을 직접 정의할 수 있다.

template<>를 통해 명시적으로 자료형을 설정하고 해당 자료형에 따른 특정 동작을 수행할 수 있도록
명시적 특수화를 main에서 수행하였는데 이러한 경우 명시적 인스턴스화(explicit Instantiation) 역시
진행되고 foo 함수를 float 인수로 하여 호출할 경우 함수에 정의된 동작(std::cout) 역시 실행된다.


템플릿 인수 명시적 특수화 (template argument explicitly specified)

템플릿 인수는 아래의 앵글 브라켓(꺽쇠)안에 있는 인자를 뜻한다. (실체화 될 때 인수로 사용)

<typename A, typename B, typename C, ... >

참고로 템플릿 인자도 default 템플릿 인자를 사용할 수 있다.

template<typename T=int>
constexpr T pi = T(3.1415);

cout << pi<> << endl; // default 템플릿 인자의 템플릿 변수가 되서 3이 출력됨

다시 볼론들어 들어가 템플릿 인수를 명시적으로 특수화하면 템플릿 인자 자료형이 모호할 때
템플릿 인수의 자료형을 확실히 나타내줘서 특수화가 어려울 때 요긴히 사용할 수 있다.
(기존 위의 특수화는 묵시적으로 템플릿 인수를 지정한 경우다.)


템플릿 인자 B와 C는 실제로 사용되지 않아서 템플릿을 특수화할 때 묵시적 템플릿 인수를 통한 매개변수의 자료형만으론 모든 템플릿 인자 자료형을 정확히 나타내기 어려워 템플릿 특수화가 되지 않는다.

이럴 때 fos<float, int, double>과 같이 템플릿의 인수형을 명시적으로 지정해줘야 한다.
템플릿을 특수화할 때 함수 이름 옆에 꺽쇠로 사용할 템플릿 인수의 자료형을 나열해주면 된다.

float fos<float, int, double>(float a, float f) {...}

템플릿 인자 typename A는 float형, typename B는 int형, typename G는 float형이라고 템플릿 인수를 명시적으로 지정하였다.

이제 템플릿의 자료형을 모두 알기에 템플릿 특수화를 시행할 수 있다.


마지막으로 템플릿 인수의 명시화도 디폴트 템플릿 인자의 영향을 받을 수 있다.

템플릿 매개변수 G에 디폴트 템플릿 인자로 int 자료형을 지정하였기에 따로 템플릿 인수형을
명시적으로 설정하지 않을 시 G가 자동으로 int형으로 취급되어 템플릿 인수를 명시화할 때
따로 G의 자료형까지 명시적으로 지정해주지 않아도 된다. (위는 A, B만 명시적으로 지정했다)

특수화 된 템플릿 함수를 호출하려 할 경우엔 caller에서 템플릿 인수형을 일치시켜줘야 한다.

fos<float, long>(56, 31.74f);
profile
언리얼 엔진 매니아입니다.

0개의 댓글