프로그래밍/cpp

C++ 템플릿, 함수템플릿에 대한 이해

콘파냐 2014. 6. 16. 16:18

STL(Standard Template Library)의 문법적 토대가 되는 템플릿에 대해서 알아보도록 하겠습니다.

템플릿을 일종의 형틀입니다. 원하는 모양을 직접 조각하거나 빗는 것은 시간이 오래걸리지만, 형틀이 있다면 단순히 찍어내기만 하면 바로 만들 수 있습니다. 이번 포스팅의 주제인 함수 템플릿은 함수를 찍어내는 형틀이라고 생각하면 될 것입니다.

먼저 간단한 템플릿 함수를 예를 들어 보겠습니다.


template <typename T> 부분은 함수 템플릿의 정의 시작 부분입니다.

선언과 정의를 따로 하려면 다음과 같이 하면 됩니다.


또한 다음과 같이 두 개 이상의 타입을 전달받을 수도 있습니다.




여기서 핵심적인 부분은 T인데, 함수의 본체에서 사용될 타입입니다. 이렇게 함수템플릿을 작성하고 단순히 이 함수를 호출을 하게되면 호출부의 인수의 타입을 읽어 그 타입에 맞는 함수를(컴파일시 컴파일러가) 자동적으로 작성하게 됩니다.


위와 같이 

T Function(T a, T b) 라고 해도 상관없지만, 구조체같이 사이즈가 큰 타입의 경우는 참조에 의한 전달이 바람직 합니다.

T Function(T &a, T &b)



사실 템플릿의 개념은 어려운 개념이 아니지만, 문법구조 자체가 복잡해 보이기 때문에, 반복을 통해 익숙해 지는 수 밖에 없습니다.


구체화

템플릿은 함수를 만들기위한 전단계, 즉 형틀입니다. 실제로 함수가 호출 될 경우 컴파일러에 의해서 함수가 만들어집니다. 그렇기 때문에 올 수 있는 모든 타입의 함수가 만들어지는 것이 아니고, 호출된 함수의 타입만 만들어지게 됩니다. 

우리는 지금 함수 템플릿을 공부했고, 이 함수 템플릿을 토대로 컴파일러가 템플릿 함수를 완성합니다. 이렇게 템플릿 함수가 만들어지는 것을 구체화라고 합니다. 구체화된 함수의 개수는 호출양상에 따라서 달라지기 때문에 하나도 호출이 안되면 구체화된 템플릿 함수는 없게 됩니다. 구체화된 함수를 VC++ 에서 확인하는 방법프로젝트->속성->링커->디버깅->맵파일생성(예/아니오) 에서 맵파일 생성을 예로 하면 Debug디렉토리에 map 파일이 생성됩니다.

어쨋건 함수 템플릿을 사용하면 각 타입에 대해서 따로 함수가 생성되기 때문에 실행파일의 크기는 커질 수 밖에 없습니다.


참고

다음 swap함수는 하나의 함수로 모든 타입을 커버할 수 있습니다.


이 함수는 아주 잘 작동합니다. 모든 타입에 대해서 작동가능하고, 템플릿처럼 각 타입마다 구체화를 하지도 않습니다. 이런면에서는 효율적이지만, 호출시 포인터를 넘겨주기 때문에 깔끔하지 않고 타입의 사이즈를 따로 넘겨줘야합니다.

(깔끔하지는 않지만, 기능적인 측면에서는 나무랄 데가 없다.)


명시적 인수 지정

명시적 인수지정은 함수 호출시에 해줍니다.

template <typename T>

T Function(T a, T b);

위 함수를 호출 할 때

Function<double>(a,b);라고 하면 Function(double a, double b); 로 템플릿 함수가 구체화 됩니다.

그런데 

Function(4.2,5) 라고 하면 에러가 나지만 명시적 구체화로 Function<double>(4.2,5) 로 호출하게되면  제대로 동작합니다. 이유는 구체화가 된 후 인수를 집어넣기 때문입니다. 일반적인 암시적 변환은 명시적 구체화의 경우는 가능하지만, 일반적인 구체화시에는 암시적 변환이 허용되지 않습니다.

이 밖에도 인수와 관련없이 타입이 사용되는 경우에 명시적 구체화가 필요 할 수 있습니다.

ex) 캐스팅

template <typename T>

T Function(int a) {

return (T)a;

}

명시적 인수지정은 특정 타입에 대해서만 함수를 구체화 하기 위해서 사용합니다.

위에서 설명한 것 처럼 템플릿 함수는 암시적 변환이 안되기 때문에 명시적 호출을 통해서 암시적 변환을 하도록 해야합니다.

double형, int형 unsigned int 형의 세가지 타입에 대해 구체화를 하는 경우를 생각해보면, 프로그램에 따라서 한가지 타입의 템플릿 함수로도 가능할 수 있습니다. 이 경우 명시적 구체화를 하여 여러벌의 구체화가 되는 것을 방지할 수 있습니다.


타입에 따른 알고리즘

템플릿은 특정 타입이 아닌 앞으로 사용되어질 가상의 타입을 지원해야 합니다. 그러나, 모든 타입에 대해서 지원되는 템플릿을 만들려면 골치가 아픈일일 거라 생각드네요. 적어도, 사용자 정의 타입은 제외하더라도 사용되어질 가능성이 있는 타입들에 대해서 문제없이 사용되어야 합니다.

배열의 경우를 예를 들어보겠습니다. 인수로 배열이 전달되는 경우 배열명을 전달하게 됩니다. 배열명은 알다시피 포인터 상수입니다.


2014/01/14 - [프로그래밍/C언어] - 상수포인터, 포인터상수, const


포인터상수기 때문에 배열끼리 값을 바꾸려면 주소값을 조작할 수 없습니다. 내용물을 바꿔야합니다. 예를 들어 보겠습니다.

먼저 단순한 Swap 템플릿을 작성하겠습니다.


타입 T에 대해서 배열명이 전달되는 경우 에러를 발생합니다. 위에서 언급했듯이 배열명은 포인터 상수기 때문에 주소값 자체를 조작할 수 없기 때문입니다. 이럴경우 Swap 함수 내부의 알고리즘을 배열에 맞게 바꿀 수도 있지만, 일반적으로 타입에 따라 적용되는 알고리즘이 달라질 경우 다음과 같은 방법으로 해결합니다.

새로운 템플릿을 만들거나 기존템플릿을 오버로딩하면 됩니다. 템플릿 또한 오버로딩이 가능하기 때문입니다. 새로운 템플릿을 만든는 경우는 간단하므로 템플릿 오버로딩으로 배열끼리의 내용교환이 가능하도록 Swap템플릿을 만들어 보겠습니다.

인수목록이 다르기 때문에 오버로딩 조건에 만족합니다.

같은맥락으로 임의의 타입에 대해서 작동하도록 템플릿을 작성해야합니다. 템플릿은 범용성을 목적에 둔 것이기 때문에 모든 필요한 타입에 대하여 제대로 작동하도록 작성해야합니다.


특수화

특정타입에대해서만 다르게 동작하도록 만드는 것.

템플릿은 알고리즘이 동일해야 합니다. 만약 특정 타입에 대해서만 다른 알고리즘을 적용시키고 싶다면 특수화로 해결하면 됩니다.

특수화문법

 template <> void Swap<float>(float &a, float &b)

 template <> void Swap<>(float &a, float &b)

 template <> void Swap(float &a, float &b)

 template void Swap<float>(float &a, float &b)

 template void Swap<>(float &a, float &b)

 template void Swap(float &a, float &b)


첫번째 표현법을 권합니다.

특수화 사용예

덧셈을 하는 Add 함수 템플릿을 작성하고  char* 경우에 는 문자를 연결하도록 하는 예제


간략하게 함수템플릿에 대한 정리를 했습니다. 클래스에대한 템플릿도 작성할 수 있는데 다음 포스팅에 정리하도록 하겠습니다.

반응형