멤버함수 템플릿
// class template definition
template <typename T>
class Stack {
public:
void Push(T a);
template <typename U>
T foo(U a);
};
// 1. 클래스 템플릿 멤버함수의 외부구현
template <typename T>
void Stack<T>::Push(T a) {}
// 2. 멤버함수 템플릿의 외부구현
template <typename T>
template <typename U>
T Stack<T>::foo(U a) {}
멤버함수 템플릿의 필요성
Complex (복소수) 클래스를 작성한다고 가정해보자. 아마도 기본적으로 아래와 같은 모양이 나올 것이다.
template <typename T>
class Complex {
public:
// Zero-initializer 사용
// T가 표준타입 혹은 포인터이면 0으로 초기화, 사용자 타입일 경우 디폴트 생성자 호출
Complex (T a = T(), T b = T()) : re(a), im(b) {}
private:
T re;
T im;
};
실수부와 허수부를 double이 아닌 T로 놓은 이유는 어떤 상황에서는 이 두 값의 타입이 int만으로 충분한 경우가 있는데, 이경우 double만으로 타입을 한정지을 경우에는 수행자체에는 문제가 없지만 성능저하가 존재한다. (double이 int에 비해 차지하는 메모리도 많고, 처리 오버헤드도 크다) 그래서 클래스를 템플릿으로 만들고, 기반 타입을 사용자가 필요에 따라서 선택하도록 하는 것이 바람직하다. 이젠 아래의 코드를 수행해보자.
Complex<int> c1(1, 3);
Complex<int> c2 = c1; //< 컴파일러가 자동생성하는 복사 생성자가 호출됨
Complex<double> c3 = c1; //< 컴파일 에러 발생, Why?
컴파일 에러의 이유 자체는 명확하다. Complex<double>와 Complex<int>는 엄연히 다른 타입이기 때문이다. 하지만 일반적으로 타입 U가 타입 T로 복사가능하다면 Complex<U>도 Complex<T>로 복사가능한 것이 자연스럽다. 이를 위해서 필요한 것이 일반화된 복사 생성자이며, 이는 아래와 같이 멤버함수 템플릿으로 구현가능하다.
template <typename T>
class Complex {
public:
Complex (T a = T(), T b = T()) : re(a), im(b) {}
// 일반화된 복사생성자(generic copy constructor)
template <typename U>
Complex (const Complex<U>& rhs);
// 모든 타입의 Complex는 private 멤버에 접근할 수 있어야 한다.
template <typename U>
friend class Complex;
private:
T re;
T im;
};
// generic copy constructor의 외부구현
template <typename T>
template <typename U>
Complex<T>::Complex (const Complex<U>& rhs)
: re(rhs.re), im(rhs.im) {}
기본적인 구현방법은 문두에서 다뤘던 멤버함수 템플릿 선언 및 구현방법과 동일하다. 그런데 하나 눈여겨 봐야 할 부분이 Complex<U> 클래스 템플릿을 friend로 선언한 점이다. 이는 Complex<T>의 멤버함수에서 Complex<U>의 (private) 멤버변수에 접근해야 하기 때문이다. 아까 언급했듯이 두 타입은 서로 다른 타입이고, 다른 타입의 private 멤버에 접근하려면 friend 키워드가 필요하다. 결론적으로 클래스 템플릿 A에 일반화된 복사생성자나 대입연산자 등을 추가하려면 위 예제와 같이 템플릿 내에 A 자신을 friend class로 선언해줘야 한다.
C++ 표준에서도 위와 같은 경우를 쉽게 접할 수 있다. 대표적으로 스마트 포인터를 예로 들 수 있다.
#include <memory>
class Animal {};
class Dog : public Animal {};
int main(int argc, char* argv[]) {
Dog* p1 = new Dog;
Animal* p2 = p1; //< 상위 클래스 포인터로 하위 클래스를 가리킬 수 있음.
// std::shared_ptr은 아래와 같은 작업들이 가능해야 한다.
std::shared_ptr<dog> p3(new Dog);
std::shared_ptr<animal> p4 = p3;
p4 = p3;
if (p3 == p4) {}
return 0;
}
위와 같이 대부분의 스마트 포인터를 만들때는 복사생성자, 대입연산자, operator ==, operator != 를 반드시 generic 버전, 즉, 멤버함수 템플릿으로 제공해야 한다. (물론 unique_ptr과 같이 예외적인 경우도 있음)
'NC University > Advanced C++' 카테고리의 다른 글
2일차 - Template 부분 전문화(2) (0) | 2015.06.30 |
---|---|
2일차 - 템플릿 인자 (0) | 2015.06.30 |
2일차 - value_type (0) | 2015.06.29 |
1일차 - new에 대해서 (0) | 2015.06.19 |
1일차 - Template meta programming (0) | 2015.06.18 |