목표
- 템플릿 ( 함수 템플릿, 클래스 템플릿 )
- 템플릿 특수화(feat. static 템플릿)
템플릿
템플릿이란?
함수나 클래스를 개별적으로 다시 작성하지 않아도, 여러 자료 형으로 사용할 수 있도록 하게 만들어 놓은 틀
[ 템플릿 종류 ]
1. 함수 템플릿
2. 클래스 템플릿 ( STL, Standard Template Library는 클래스 템플릿으로 구성 )
[ 템플릿 장단점 ]
장점 :
- 모든 자료형을 호환 ( 클래스도 호환 )
- 컴파일러가 자료형 검사를 수행하기 때문에 안전
- 컴파일러 차원에서 인스턴스에 의한 코드 생성을 하기 때문에 속도 빠름
// 기존의 함수, 클래스는 기능이 같아도 매개변수, 반환타입의 자료형에 따라 각각 함수를 직접 선언해줬어야 함
// 각 자료형에 따른 함수를 생성하여 호출하는 원리는 똑같지만, 템플릿은 컴파일러가 생성하기 때문에 빠름
단점 :
- 자료형에 따라 매번 생성하기 때문에 실행파일이 커지고 더불어 코드 비대화의 가능성이 열려있다.
■ 기능 / 주의
- 생성 과정 (컴파일 과정)에서 기능은 정해져 있으나, 자료형은 런타임 때 정해진다.
- 템플릿은 클래스 내에 몸체까지 구현해주는 경우가 많다. ( 헤더파일에 전부 정의 )
- 템플릿도 상속이 가능하다, 상속 받는 부모도 클래스명<자료형>으로 템플릿임을 밝혀야 한다.
- ex. CPlayer : public CObj<T>
템플릿 매개변수 선언 시 class와 typename은 동의어다.
템플릿 매개 변수 선언 시 사용되는 typename 또는 class는 동의어이므로 바꿔 써도 상관이 없다.
다만, 중첩 의존 타입 이름을 식별하는 용도에는 반드시 typename을 사용한다.
중첩 의존 이름이 기본 클래스에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우는 예외다.
template<typename T>
class Widget;
template<class T>
class Widget;
함수 템플릿
함수 템플릿이란?
함수의 모양과 유사한 형태를 띈 템플릿 문법 (ex. STL, 알고리즘, 조건자)
자료형을 인식하는 순간 내부적으로 그 자료형에 해당하는 함수를 생성하여 호출
[ 단항 연산자 ] : 함수의 자료형이 한 타입인 경우
template<typename T> // T 대신 다른 이름 사용 가능
T 함수명(T 변수명, T 변수명) // 인스턴스화에 의해 내부적으로 함수가 생성됨
{
몸체코드;
}
void main(void)
{
함수명<T에 들어갈 자료형>(매개변수);
}
-
template <typename T> // T 대신 다른 이름 사용 가능
T Sum(T _A, T _B)
{
return _A + _B;
}
void main(void)
{
cout << Sum<float>(10, 10) << endl; // 명시적 선언, 20f
cout << Sum(10.7, 10.7) << endl; // 묵시적 선언, float로 인식, 21.4
}
-
[ 다항 연산자 ] : 함수의 자료형이 여러 타입인 경우
template<typename T1, typename T2> // 다른 종류의 자료형마다 typename 선언
T2 함수명(T1 변수명, T2 변수명) // 인스턴스화에 의해 내부적으로 함수가 생성됨
{
몸체코드;
}
template<typename T1, typename T2, typename T3>
T3 함수명(T1 변수명, T2 변수명)
{
몸체코드;
}
void main(void)
{
// 모양에 맞는 템플릿으로 실행됨
// 템플릿은 들어간 자료형에 해당하는 함수를 생성하여 호출/실행함.
함수명<자료형1, 자료형2>(인수1, 인수2); // 자료형1, 2 타입으로 인수1, 2 들어감, 자료형2 타입으로 반환됨
함수명<자료형1, 자료형2, 자료형3>(인수1, 인수2); // 자료형1, 2 타입으로 인수1, 2 들어감, 자료형3 타입으로 반환
}
※ 템플릿 사용 시 함수명<자료형>(매개변수) 꼴로 자료형을 명시적으로 선언해주는 것을 권장
- 함수명(매개변수)도 사용 가능하긴 함, 묵시적으로 컴파일러가 매개변수에 따른 자료형으로 함수 생성
■ 기능 / 주의
- 임의의 자료형 T, 런타임 때 T에 들어온 자료형을 보고 해당 자료형의 함수를 생성한다.
- 함수 템플릿 자체는 함수가 아닌 틀이며, 함수를 생성하여 사용하는 원리는 똑같음
- 다항 템플릿의 경우 typename이 여러 개일 때, typename의 수만큼 명시적 선언<자료형>을 해줘야함
- 묵시적 선언을 원할 경우, typename의 수와 매개변수의 수가 같으면 묵시적으로 함수 생성 (명시적으로 해줄것)
- 그렇지 않은 경우 서로 다른 매개변수를 무슨 타입으로 반환해야할지 모르기 때문에 오류 - 다항 템플릿의 경우, 다른 typename으로 템플릿을 선언했을 때 같은 자료형으로 넘겨줘도 상관없다.
■ 예시
#include "stdafx.h"
// 단항 템플릿
template <typename T>
T Sum(T _A, T _B)
{
return _A + _B;
}
// 다항 템플릿
template <typename T1, typename T2, typename T3> // 사실 이렇게 쓸거면 T3 없애야징
T1 Sum(T1 _A, T2 _B)
{
return _A + _B;
}
template <typename T1, typename T2, typename T3>
T3 Minus(T1 _A, T2 _B)
{
cout << _A - _B << endl;
}
void main(void)
{
float fA = 10.7f, fSum = 0;
int iA = 1, iSum = 0;
// 서로 다른 자료형이 들어올 때는 반환값을 뭘로 해줘야할지 모르기 때문에 반환타입도 정해줘
// 매개변수가 int형으로 들어가서 int 타입으로 반환되기 때문에
// fA의 10.7f는 10으로 짤려서 매개변수로 들어가게 됨, 20반환
cout << Sum<int>(fA, fA) << endl; // 20
// 매개변수가 float형으로 들어가서 float 타입으로 반환되기 때문에
// fA의 10.7f는 21.4f로 반환됨
cout << Sum< float>(fA, fA) << endl; // 21.4
iSum = Sum< float>(fA, fA); // 반환된 21.4f가 int형인 iSum에 대입되면서 21로 짤림
cout << iSum << endl; // 20
// <typename>이 여러 개일때, typename 수만큼 <명시적 선언> 해줘야됨
// 묵시적 선언을 하려면 typename의 수와 매개변수의 수가 같으면 묵시적으로 해당 자료형으로 함수 생성
// 다른 종류의 typename끼리 들어가는 자료형이 동일해도 문제 없음
cout << Sum<int, float, int>(iA, fA) << endl; // int, float로 전달, int로 반환, 1 + 10.7 = 11.7, 11로 반환
Minus<float, int, void>(fA, iA); // float, int로 매개변수, void로 반환
}
#include "stdafx.h"
#include <string>
template<typename T>
class Vector
{
private:
T A, B;
public:
Vector() : iA(0), iB(0) {}
Vector(T _A, T _B) : A(_A), B(_B) {}
~Vector() {}
public:
Vector operator+(Vector rhs)
{
// 변수명 안 정하고 객체만 생성해서 return해도 됨 ( 어차피 넘겨주고 사라질 지역 객체니까 )
/*Vector Temp(A + rhs.A, B + rhs.B);
return Temp;*/
return Vector(A + rhs.A, B + rhs.B);
}
};
template<typename T>
T strcatArray(T array[], int _iSize) // 문자열 결합 함수
{
T Array = {};
for (int i = 0; _iSize > i; ++i)
{
Array += array[i]; // 한 변수에 배열 안에 들어있는 값을 계속해서 더해서 붙여넣기
}
return Array; // 배열을 반환하면 주소가 아니라 배열이 가는 것 ?
}
void main(void)
{
// 클래스 테픔릿의 경우 <자료형>을 꼭 명시해 줄 것 , 함수 템플릿도 명시해줄 것을 권장
Vector<float> v1(10, 20);
Vector<float> v2(100, 200);
// v1.operator+(v2); 와 같음, Vector 객체 return
// v3 = 반환된 Vector 객체, 대입 연산자(디폴트 연산자) 실행, (1대1 단순 대입)
Vector<float> v3 = v1 + v2;
//Vector<int> v3 = v1 + v2; // 연산 오버로딩은 클래스의 자료형이 전부 같아야만 가능
int iArray[5] = { 1, 2, 3, 4, 5 }; // sizeof(iArray)는 5 * 4byte = 20;
string sArray[3] = { "I", "want", "Finish" }; // sizeof(sArray)는 3 * string일텐데 string 크기 미측정, sizeof(sArray)는 82가 나옴
string sA = strcatArray<string>(sArray, sizeof(sArray)/sizeof(string));
int iA = strcatArray<int>(iArray, sizeof(iArray) / sizeof(int));
cout << "<string>형 문자형 결합 : " << sA << endl; // IwantFinish
cout << "<int>형 문자열 결합 : " << iA << endl; // 15
}
※ 클래스 안의 템플릿 함수는 호출 불가하다.
[ 함수 템플릿을 이용한 동적할당 해제 ]
매크로 대신 템플릿을 이용하여 동적할당 해제 선호 ( 컴파일 타임에 디버깅을 할 수 있다는 이점 때문 )
template<typename T>
void Safe_Data(T& _p)
{
if(_p)
{
delete _p;
_p = nullptr;
}
}
클래스 템플릿
클래스 템플릿이란?
클래스 모양과 유사한 형태를 띈 템플릿 문법 ( STL의 템플릿은 클래스 템플릿 )
template<typename T1>
class CPlayer
{
private:
T1 멤버변수
public:
생성자() {} // 기본 생성자
생성자(T1 매개변수) : 멤버변수(매개변수) {} // 매개변수 생성자
public:
T1 Get_Data(void) { return 멤버변수; }
}
☞ 클래스 템플릿은 멤버변수와 생성자에 들어올 매개변수를 위주로 임의의 자료형 T를 두는 듯함
■ 기능 / 주의
- 클래스 템플릿의 경우 호출 시 <자료형>을 꼭 명시적으로 선언해줄 것
- CDainn<int> cd; - 클래스 템플릿은 정의부 따로 선언 시 ' 반환타입 클래스명<자료형>::함수명() {몸체} ' 로 템플릿임을 꼭 밝힐 것
- 클래스가 아니기 때문에 ' 반환타입 클래스명::함수명() {몸체} ' 꼴로 사용할 수 없음
- ex. template<typename T>를 밝힌 후 void CDainn<T>::Get_Data() { 몸체 }
■ 예시
template<typename T>
class CDainn
{
public:
T Return(T t);
private:
T m_T;
};
template<>
class CDainn<int>
{
public:
CDainn() : m_iA(0) {}
int Return(int i);
private:
int m_iA;
};
// 정의부
template<typename T>
T CDainn<T>::Return(T t) { return m_T=t; }
int CDainn<int>::Return(int i) { return m_iA + i; }
void main(void)
{
CDainn<int> cd;
CDainn<char> cc;
cout << cc.Return('b') << endl;
cout << cd.Return(10);
}
※ 템플릿 특수화 부분만 단독으로 정의 불가능, 본체 부분과 함께 정의해야함
템플릿 특수화
템플릿 특수화란?
기존 템플릿이 존재할 때, 특정 자료형의 경우 다른 기능을 구현하고 싶을 때 사용하는 문법
template<typename T1, typename T2>
class CPlayer
{};
// 부분 특수화 예 1
template<>
class CPlayer<int, char*>
{};
// 부분 특수화 예 2
template<typename T>
class CPlayer<T, char*>
{};
※ 함수 템플릿 특수화는 없음 ( 오버로딩을 이요하면 가능하다는데 굳이? )
[ Static 템플릿 ]
: 템플릿 안의 정적 멤버, 정적 멤버는 클래스 밖에서 정의해줄 것.
- 정적 멤버는 무조건 클래스 외부에서 정의해줄 것. ( 멤버 변수 중 정적 멤버만 외부에서 정의 가능 )
- 외부에서 정의 시 소속을 밝힐 때, 클래스<T> 까지 꼭 작성할 것 ( <T>자료형을 넣을 템플릿임을 알려줌 )
template<typename T>
class CDainn
{
private:
T temp;
static T stemp;
int i;
public:
CDainn() : temp(0) {}
CDainn(T A) : temp(A) {}
public:
T Get_temp(void) { return temp; }
T Plus(T _iA) { temp += _iA; return temp; }
public:
T Get_stemp(void) { return stemp; }
T sPlus(T _iA) { stemp += _iA; return stemp; }
};
// 템플릿 static 멤버는 정의부를 무조건 템플릿 클래스 외부에.
template<typename T> // 템플릿 밖에서 <T>가 무엇인지 알기 위해 선언
T CDainn<T>::stemp = 10; // 소속은 CDainn<T> 자료형까지 꼭 밝혀줄 것
/* 정적 멤버만 템플릿 클래스 외부에서 초기화 할 수 있다.
template<typename T>
T CDainn<T>::temp = 0;
template<int>
int CDainn<int>::i = 0; */
void main(void)
{
// cd와 cd2는 CDainn 타입의 서로 다른 객체이다.
// 개별로 멤버를 가지고 있기 때문에 cd의 temp는 따로, cd1의 temp는 따로이다.
// 물론 한 객체 안에서 Plus()를 해주면 해당 객체의 멤버 안의 수는 유지된다.
// static을 해주면 서로 다른 객체여도 temp는 Data영역에 저장되어 있기 때문에 공유된다.
CDainn<int> cd(10);
CDainn<int> cd2(100);
cout << "일반 멤버 변수 temp의 경우" << endl;
cd.Plus(5); // cd.temp는 10으로 생성됨, +5 // 15
cd.Plus(5); // 15 + 5 = 20
cout << cd.Get_temp() << endl; // 20
cd2.Plus(50); // cd2.temp는 100으로 생성됨, +50 // 150
cd2.Plus(50); // 150 + 50
cout << cd2.Get_temp() << endl; // 200
// static T stemp의 경우
cout << "static 멤버 변수 stemp의 경우" << endl;
cd.sPlus(5); // cd.temp는 = 0, +5 // 5
cd.sPlus(5); // 5 + 5
cout << cd.Get_stemp() << endl; // 10
cd2.sPlus(50); // cd2.temp는 CDainn temp 이어서, 10 + 50 // 60
cd2.sPlus(50); // 60 + 50
cout << cd2.Get_stemp() << endl; // 110
}
참고문헌
[1] 스콧 마이어스, <이펙티브 C++ 3판>, 번역 곽용재, 피어슨에듀케이션코리, 2006, pp.300-320
'C++' 카테고리의 다른 글
| [TIL 29장] 컨테이너 함수(feat. deque), 반복자 (0) | 2022.09.15 |
|---|---|
| [TIL 28장] STL 개론(feat. 자료구조), 컨테이너, 시간 복잡도, 벡터 (0) | 2022.09.14 |
| [TIL 26장] 연산자 오버로딩, 함수 객체, 함수 포인터, inline (0) | 2022.09.13 |
| [TIL 25장] 캐스팅(업캐스팅, 다운캐스팅), 캐스팅 연산자 (0) | 2022.09.08 |
| [TIL 24장] 순수 가상 함수 (0) | 2022.09.08 |