함수와 연산자에 대한 오버로딩을 진행하면서 타입별로 하나하나 해줘야 했었던 불편함을 떠올려보자.
가령 제곱에 대해 오버로딩을 진행하려면 int 가 들어왔을 때, double이 들어왔을때 따로따로 해줬어야 했다.
혹은 #define 을 통해 처리했어야 했다.
하지만 이미 알고 있듯이 #define 의 정의가 힘들기 때문에 함수형으로 사용하고 싶다면 템플릿에 대해 공부해보도록 하자.
함수 템플릿(function template)
템플릿은 어떤 물체를 만들어내는 틀이라고 생각할 수 있다.
템플릿은 template 키워드를 사용한다.
#include <iostream>
using namespace std;
template <typename T>
T SQR(T a) { return a * a; }
int main() {
int a = 10;
cout << SQR<int>(a) << "\n";
double b = 20.;
cout << SQR<double>(b) << "\n";
}
짜잔~
마법같은 함수 테블릿 완성!
template 을 이용하여 T 라는 녀석을 typename(혹은 class 를 써도 동일) 으로 사용할 것이란 것을 선언해주고,
typename 이 들어가야 하는 곳에 T를 쓰고 나면 컴파일러는 SQR<> 이 사용될 때 해당 타입에 대한 함수를 새로 작성한다.
즉 SQR<int>(a) 가 사용된다면 int SQR(int a) {return a*a;} 라는 함수를 새로이 작성한다.
double 또한 마찬가지이다.
물론 한 번 만들어진 함수는 두 번 만들지 않고, 컴파일 시에 새로 만드는 것이므로 실행시간에는 영향이 없다.
또한 SQR<int>(a) 는 사실 SQR(a) 와 동일한데, 이는 컴파일러가 자료의 손실이 없는 쪽으로 타입을 맞추어 알아서 판단해서 생성해주기 때문이다.
이렇게 template을 사용하여 정의한 함수를 함수 템플릿(function template)이라고 부르고,
컴파일러가 type별로 새로 만들어내는 함수들을 템플릿 함수(template function)이라고 부른다.
즉 전자는 함수를 만들어내는 틀이고, 후자는 틀에서 만들어진 함수라는 뜻이다.
템플릿은 두 개 이상의 타입도 가능하다.
#include <iostream>
using namespace std;
template <typename T1, typename T2>
void print(T1 a, T2 b) {
cout << "a : " << a << " b : " << b << endl;
}
int main() {
print<char, int> (65, 10.1);
}
int 형인 65를 대입했지만 T1은 char형으로, 10.1은 double 형이지만 T2에 int 를 넣었으므로 A와 10이 나온다.
만약 오버로딩이 필요한 분야라면 어떨까?
가령 특정 class 타입을 서로 더해야 하는데 연산자+가 오버로딩이 되어있지 않다면 템플릿 함수는 아무 의미를 가지지 않는다. 실제로 계산이 되지 않기 때문이다.
이런 상황에서도 쓸 수 있는데, 이를 함수 템플릿의 특수화(Specialization)라고 부른다.
다음의 코드는 위의 예시와 부합하지 않지만 간편한 설명을 위해 그냥 제시해본다.
#include <iostream>
using namespace std;
template <typename T1, typename T2>
void print(T1 a, T2 b) {
cout << "a : " << a << " b : " << b << endl;
}
template<>
void print(const char* a, const char* b) {
cout << "A : " << a << " B : " << b << endl;
}
int main() {
print<char, int> (65, 10.1);
print("Hello", "world");
}
위의 결과가 어떻게 보이는가.
물론 const char* 에 대한 정의를 하지 않았더라도 컴파일러에 의해 자동으로 생성되었을 것이다.
하지만 만약 이 예시가 오버로딩이 필요한 함수였다면 첫 번째 print 함수는 의도하지 않은 동작을 하는 의미 없는 함수가 될 것이다.
밑의 특수화한 코드가 의미가 있다고 생각하고 보자.
컴파일러는 해당하는 함수 템플릿이 이미 선언되어 있을 경우 새로 생성하지 않고 내가 작성한 함수를 실행하게 된다.
사용법은 template<> 을 선언하고 내가 원하는 함수를 선언해주면 된다.
클래스 템플릿(class template)
함수 템플릿과 동일하게 클래스에도 템플릿을 적용할 수 있다.
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class TemplateClass1 {
public:
void print(T1 a, T2 b) {
cout << a << " " << b << endl;
}
T1 asdf(T1 a);
};
template <typename T1, typename T2>
T1 TemplateClass1<T1, T2>::asdf(T1 a) {
return a;
}
int main() {
TemplateClass1<int, char> c;
c.print(10, 'a');
}
함수와 마찬가지로 선언하면 되고, 외부에서도 정의가 가능하다.
외부에서 정의할 때는 반드시 template<typename .> 을 위에 적어줘야하고,
반환타입과 호출될 때 타입 TemplateClass1<T1,T2> 을 명시해야한다.
물론 T1과 T2 가 사용되지 않는 정의라면 template<> 만 선언해도 된다.
템플릿 함수와 다르게 호출하려는 타입을 명시해야하며, 선언과 정의가 각각 다른 파일에 있을 경우 컴파일러가 이해하지 못하기 때문에 (하나씩만 컴파일하기 때문) 생성자와 멤버함수의 정의가 함께 있어야한다. (아니면 .h랑 .cpp둘 다 include 하던지)
템플릿 클래스는 서로 다른 템플릿 클래스를 가리킬 수 있으며, 함수와 마찬가지로 특수화 시킬 수 있다.
반드시 <type> 을 클래스명 뒤에 붙여주도록 하자.
또한 디폴트 값도 사용이 가능한데 <typename T1 = int , typename T2 = int> 식으로 만들어준다면, <>만 입력했을 때 int ,int 로 처리한다.
template type과 static의 결합도 가능하다.
물론 이것은 컴파일러가 해당 클래스를 만들어줄 때 단 한 번만 초기화되기 때문에 기존 static 멤버와 동일하게 취급하되 type만 신경써서 보면 된다.
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class TemplateClass1 {
private:
static T1 mem;
public:
void print(T1 a, T2 b) {
cout << a << " " << b << endl;
}
T1 asdf(T1 a);
};
template< typename T1, typename T2>
T1 TemplateClass1<T1, T2>::mem = 0;
template <typename T1, typename T2>
T1 TemplateClass1<T1, T2>::asdf(T1 a) {
return a;
}
int main() {
TemplateClass1<int, char> c;
c.print(10, 'a');
}
물론 static은 한 번만 정의되어야 하기 때문에 외부에서 한 번만 정의한다.
(T1 TemplateClass1<T1,T2>::mem =0;)
T1 은 mem 과 동일한 타입으로 해야한다.
이렇게 깊게 들어가지 않고 간단하게만 template을 살펴봤다.
윤성우님은 STL의 사용을 위해서 template의 이해가 단단해야 한다고 말씀하시지만,
STL을 다루지 않고 JAVA로 넘어갈 예정이기 때문에 우선은 이정도로만 알아보도록..하자...(힘드러..)
자료구조는 알고리즘을 하면서 어느정도 익혔기 때문에 급하지 않아서, 우선은 C++에 대해 이해를 하고 추가적으로 공부를 해보도록 하자!
클래스는 함수와 다르게 항상 <type>을 붙여서 사용해줘야 한다는 것만 잘 기억해서 쓰면 아주 편안하게 오버로딩이나선언 및 정의를 할 수 있을 것 같다.
'C언어' 카테고리의 다른 글
C 와 C++ 의 차이 (0) | 2022.02.21 |
---|---|
C++ 프로그래밍 - 예외처리(Exception Handling)와 형변환 연산자(type casting operator) (0) | 2021.05.10 |
C++ 프로그래밍 - 연산자 오버로딩(operator overloading) (0) | 2021.05.09 |
C++ 프로그래밍 - 클래스의 상속(Inheritance)과 다형성(polymorphism) (1) | 2021.05.06 |
C++ 프로그래밍 - 클래스의 기초 (0) | 2021.05.05 |