프로그래밍 지식/C++

C++문법/ (함수) 템플릿, Template - 1

게임이 더 좋아 2021. 12. 14. 20:20
반응형
728x170

 

 

템플릿이란 무엇이냐?

 

하나의 클래스를 서로 다른 (사용자가 원하는) 타입에 재사용할 수 있도록 하는 것이다.

타입만 넣어주면 자동으로 코드를 찍어내는 틀이라고 보면 된다.

 

예를 들어서 여러 타입의 객체를 저장할 수 있는 연결리스트와 같은 자료구조를 만들 수 있다.

**template <typename T> 와 template <class T> 는 정확히 같은 의미를 같지만

typename키워드를 많이 쓴다.

//template class를 만들었다.
template class ShiftedList {
        T* array; // 특정 타입의 배열
        int offset, size;
    public:
        ShiftedList(int sz) : offset(0), size(sz) {
            array = new T[size]; // 타입 T의 배열
         }
         
         ~ShiftedList() {
             delete [] array;
         }
         
         
         void shiftBy(int n) {
             offset = (offset + n) % size;
         }
         
         T getAt(int i) {
             return array[convertIndex(i)];
         }
         
         void setAt(T item, int i) {
             array[convertIndex(i)] = item;
         }
         
     private:
         int convertIndex(int i) {
            int index = {i - offset) % size;
            while(index < 0) index += size;
            return index;
         }
};

 


 

템플릿에서 더 발전해서

함수 템플릿이라는 말이 나왔다.

함수 템플릿은 템플릿의 뜻을 따라 함수의 일반화라고 볼 수 있다.

 

즉, 함수 템플릿은 int 형이나 double 형과 같은 구체적인 데이터형을

포괄할 수 있는 일반형(generic type)으로 함수를 정의하는 것이다.

 

어떠한 데이터 타입을 템플릿에 매개변수로 전달하면

컴파일러가 그 데이터 형에 맞는 함수를 생성하는 것이다.

 

템플릿은 구체적인 데이터 타입을 정하는 대신에 일반형으로 프로그래밍을 하게 되므로,

이것을 일반화 프로그래밍이라고도 부른다.

또한 데이터 형이 매개변수에 의해 표현되므로 템플릿을 때로는 매개변수화 데이터형 이라고도 한다.

 

템플릿 기능이 왜 유용한지, 그리고 어떻게 작동하는지 알아보자.

 


 

템플릿은 무엇인가 만드는 틀이다. 

 

그렇다면 함수 템플릿은 무엇일까? 

함수를 만드는 틀이다.

 

그렇다면 어떠한 타입에 대해서도 만들 수 있는건가?

일반적으로는 그렇다. 함수 템플릿에 대해서 알아보자

 

함수 템플릿에서는 임의의 데이터타입으로 함수를 정의하는 것을 허용한다.

-> 데이터 형을 잘못쓰거나 변수가 잘못되는 경우가 생김

(함수 템플릿에서는 이 과정을 자동화해서 코드의 신뢰성을 높임)

 

예를 들어보자

두 변수의 값을 교환하는 함수를 템플릿으로 만들어보자.

template <class Any> // 함수를 템플릿으로 설정하고 임의의 데이터타입을 Any로 정한다는 뜻
// 동치인 문장 -> template <typename Any>
void Swap(Any &a, Any &b){
    Any temp;
    temp = a;
    a = b;
    b = temp;
}

 

함수 작성 이전에 template 과 class 또는 typename을 꼭 반드시 선언하고 작성해야 한다.

데이터 타입의 이름은 꼭 Any가 아니어도 규칙만 지켜서 잘 지으면 된다.

**대부분의 프로그래머가 T라고 정하고 사용하니 Docs들을 볼 때 참고하자.

 

참고로 함수의 모든 매개변수가 꼭 일반형(T와 같은)일 필요는 없다. 섞여있어도 무방하다.

 

여기서 템플릿의 역할 자체는 함수를 만드는 것이 아니다.

함수를 정의하는 방법을 컴파일러에게 알려주는 것이다.

int 값을 교환하고 싶다면 

컴파일러가 템플릿에 따라 Any를 int로 바꾸어 적당한 함수를 생성하는 것이다.

마찬가지로 double 값을 교환하고 싶다면

컴파일러가 템플릿에 따라 Any를 double로 바꾸어 적당한 함수를 생성한다.

 

**즉, 우리가 함수 템플릿을 쓰는 이유는

다양한 데이터형에 대해 아예 동일한 알고리즘을 적용해야 할 때, 그 때 사용한다.

 

?? 오버로딩과 비슷해보이는데???

비슷한 것은 사실이다.

그러나 오버로딩같은 시그니처를 가져서는 안되고

매개변수에 따라 조금씩 다르게 동작할 수 있다.

 

하지만 함수 템플릿은 동일한 시그니처에 동일한 알고리즘을 적용한다.

이 차이가 있다.

 

사용하는 법은 어렵지 않다.

파일 위쪽에 함수 템플릿 원형을 가져다 놓고

main( ) 뒤에 템플릿 함수 정의를 넣는 일반적인 함수와 같다.

 


 

흠.. 그럼 만약 같은 타입이 아니라 다른 타입이라면..?

그니까 인수 종류가 여러 개 필요하면...?

 

상관없다.

원하는 만큼 인수를 정하면 된다.

template <typename T1, typename T2>

 


 

그렇다면 템플릿 함수는 뭘까??

엥 함수 템플릿이랑 다른게뭐야??

 

함수 템플릿은 템플릿이다. 실제 함수가 아니라 이런 식으로 만들어진다는 틀이다.

템플릿 함수는 실제 위의 함수 템플릿을 이용해서 만든 함수다.

 

틀 자체는 찍어내지 않으면 아무 소용이 없듯이 찍어내면 템플릿 함수가 되어 쓸 수 있는 형태가 된다.

다만 이제 틀을 정말 잘 만들어야 한다.

 

우리가 좋은 거푸집을 만들어, 청동 검, 철제 검, 강철 검, 티타늄 검, 다이아몬든 검을 만들듯이

거푸집을 대충만들면 뒤에 나오는 결과물도 형편없을 것이라 예상된다.

그래서 우리가 템플릿을 잘 정의해야할 필요성이 생긴다.

 

**또 템플릿이 항상 좋은 것은 아니다.

매 타입을 만날 때마다 새로운 함수가 생성되므로 구체화된 수만큼 실행 파일의 크기가 커진다.

한 두개쯤이면 상관이 없겠지만 숫자가 점점 늘어날수록 메모리 측면에서 손해를 보기 시작한다.

만약 메모리에 신경을 쓴다면 void* 를 사용해서 임의의 데이터를 처리하는 방식이 오히려 더 유리하다.

 


 

위에서 보았듯이 swap 함수는 양쪽에 같은 타입의 인수를 받는다.

다시 말해서 다른 타입의 인수 2종류가 들어오면 오류가 난다는 말과 같다.

예를 들어보자

template <typename T>

T max(T a, T b)
{
    return (a>b) ? a : b;
}

 

두 값 중에 큰 값을 반환하는 함수다.

 

만약 max(1,3)이면 무엇이 반환될까??

3인 것이 뻔하다.

 

그렇다면 max(1, 2.2)는??

2.2가 반환될까??

물론 1를 실수로 상승 변환해서

2.2를 정수로 하강 변환해서 해결할 수 있지만

개발자가 의도했는지 판단하기엔 부족하다.

 

다른 인수가 들어갔을 때 정확하게 결과를 이끌어 내고 싶다면

우리는 "명시적으로 인수"를 알려줘야 한다.

 

함수명 다음에 < > 괄호로 템플릿 인수가 무엇으로 했야한다고 명시적으로 선언하는 것이다.

int x = max<int>(1,2.2);

그렇게 되면 2.2가 정수로 하강변환해서 2를 반환할 것이다.

 

타입 인수를 리턴값이나 지역 변수에 적용하는 템플릿도 명시적 타입 지정이 필요하다.

리턴 값이나 지역 변수는 함수 호출문에 나타나지 않아서

호출문만으로는 구체화할 함수를 결정을 할 수가 없다.

그렇기 때문에 어떤 함수를 원하는지 지정을 해줘야 한다.

 

이런 경우에는 객체를 대신 생성해주는 래퍼 함수를 만들 때 가끔 사용된다.

 

예를 볼까?

template <typename T>
T cast(int s)
{
    return (T)s;
}

 

이렇게 하면 어떻겠는가?

 

unsigned u = cast <unsigned>(1234);

 

오류가 나올까?

전혀 아니다.

unsigned로 타입캐스팅해서 반환하게 된다.

다만 명시적으로 호출할 때 타입을 선언해야 작동한다.

즉, 위와 같이 만들었다면 무조건 명시적으로 선언할 의무가 생긴다.

 

 


 

템플릿을 만들 때 주의할 것이 있다.

템플릿은 임의의 타입에 대해 동작하기 때문에

특정 타입에 종속적으로 작동하는 코드들을 넣으면 위험하다.

즉, 기본 타입을 모두 지원하는 +,- 연산자 같은 것들을 사용하거나 cout 처럼 피연산자의 타입을 스스로 판별할 수 있는 코드만을 사용해야 한다.

즉, printf처럼 타입마다 %d, %s, %c처럼 바뀌는 서식을 가지고 있으면 안된다는 말이다.

다만 cout에서는 또 출력이 되는 것도 스스로 판별할 수 있는 출력함수이기 때문이다.

 

만약 새로운 클래스에 대해 템플릿을 사용하고 싶다면

새로운 클래스에도 +,- 같은 operator에 대한 기본연산이 정의되어 있다면

종속성에서 벗어날 수 있다는 말이다.

 

아무튼 어떠한 특정 타입의 종속적인 코드를 쓰면 좋지 않다는 말이다.

 

반응형
그리드형