31-2.클래스 템플릿

31-2-가.타입만 다른 클래스들

클래스 템플릿은 함수 템플릿과 비슷하되 찍어내는 대상이 클래스라는 것만 다르다. 구조나 구현 알고리즘은 동일하되 멤버들의 타입만 다를 경우 클래스를 일일이 따로 만드는 대신 템플릿을 정의한 후 템플릿으로부터 클래스를 만들 수 있다. 실용적 가치는 별로 없지만 화면상의 특정 좌표에 출력될 값에 대한 정보를 표현하는 클래스를 만들어 보자. 정보의 타입에 따라 값을 표현하는 멤버의 타입이 달라지므로 타입에 따라 클래스를 일일이 만들어야 한다.

 

class PosValueInt

{

private:

     int x,y;

     int value;

public:

     PosValue(int ax, int ay, int av) : x(ax),y(ay),value(av) { }

     void OutValue();

};

 

class PosValueChar

{

private:

     int x,y;

     char value;

public:

     PosValue(int ax, int ay, char av) : x(ax),y(ay),value(av) { }

     void OutValue();

};

 

class PosValueDouble

{

private:

     int x,y;

     double value;

public:

     PosValue(int ax, int ay, double av) : x(ax),y(ay),value(av) { }

     void OutValue();

};

 

좌표값 x, y는 모든 클래스에서 int형이며 값을 표현하는 value 멤버의 타입만 달라진다. 클래스는 함수에서와 같은 오버로딩이 지원되지 않으므로 이름을 모두 다르게 작성해야 하며 value의 타입에 따라 생성자의 원형도 각기 다르다. 결국 실제로 다른 부분은 value의 타입뿐이며 나머지는 모두 동일하므로 이 클래스들을 하나의 템플릿으로 통합할 수 있다.

 

: PosValueTemp

#include <Turboc.h>

#include <iostream>

using namespace std;

 

template <typename T>

class PosValue

{

private:

     int x,y;

     T value;

public:

     PosValue(int ax, int ay, T av) : x(ax),y(ay),value(av) { }

     void OutValue();

};

 

template <typename T>

void PosValue<T>::OutValue()

{

     gotoxy(x,y);

     cout << value << endl;

}

 

void main()

{

     PosValue<int> iv(1,1,2);

     PosValue<char> cv(5,1,'C');

     PosValue<double> dv(30,2,3.14);

     iv.OutValue();

     cv.OutValue();

     dv.OutValue();

}

 

클래스 선언문앞에 template <typename T>를 붙이고 타입에 종속적인 부분에만 T를 사용하면 된다. 예제의 PosValue 클래스는 템플릿 인수로 전달받은 타입 T를 value의 타입으로 선언하였고 생성자의 세 번째 인수도 T형이 된다. 이렇게 정의된 클래스 타입의 객체를 생성할 때 클래스 이름 다음의 < > 괄호안에 원하는 타입을 밝혀야 한다. 클래스 템플릿으로부터 만들어지는 클래스를 템플릿 클래스라고 하는데 템플릿 클래스의 타입명에는 < > 괄호가 항상 따라 다닌다. value가 int형인 클래스의 이름은 PosValue<int>이고 value가 char형인 클래스의 이름은 PosValue<char>이다.

단 예외적으로 생성자의 이름은 클래스의 이름을 따라가지만 클래스 템플릿의 경우 템플릿 이름을 사용해도 상관없다. <T> 괄호가 있거나 없거나 상관없다는 얘기인데 위 예제의 PosValue 생성자를 PosValue<T>(int ax, int ay, T av)로 정의할 수도 있다. 보통은 생성자에 대해서는 <T>를 붙이지 않는다.

클래스 템플릿의 멤버 함수를 선언문 외부에서 작성할 때는 템플릿에 속한 멤버 함수임을 밝히기 위해 소속 클래스의 이름에도 <T>를 붙여야 하며 T가 템플릿 인수임을 명시하기 위해 template <typename T>가 먼저 와야 한다. OutValue 멤버 함수는 PosValue<T> 클래스 소속이며 이때 T는 템플릿 인수 목록으로 전달된 타입의 이름이다. 함수 본체 내에서는 T를 언제든지 참조할 수 있다. 클래스 선언문 내부에서 인라인으로 함수를 선언할 때는 클래스 선언문앞에 T에 대한 설명이 있으므로 이렇게 하지 않아도 상관없다.

 

template <typename T>

class PosValue

{

     ....

     void OutValue() {

          gotoxy(x,y);

          cout << value << endl;

     }

};

 

템플릿 클래스로부터 객체를 선언할 때는 템플릿 이름 다음에 < >괄호를 쓰고 괄호안에 T로 전달될 타입의 이름을 명시해야 한다. PosValue<int>는 int타입의 value를 멤버로 가지는 PosValue 템플릿 클래스를 의미하며 PosValue<double> 클래스의 value는 double 타입이 된다. 템플릿 클래스의 이름에는 타입이 분명히 명시되어야 한다. PosValue라는 명칭은 어디까지나 템플릿의 이름일 뿐이므로 이 이름으로부터 객체를 생성할 수는 없다.

컴파일러는 객체 선언문에 있는 초기값의 타입으로부터 어떤 타입에 대한 클래스를 원하는지 알 수 있을 것도 같다. 예를 들어 PosValue iv(1,1,2)라고 쓰면 제일 마지막 인수가 int형 상수이므로 PosValue<int> 타입이라고 유추 가능할 것이다. 그러나 생성자가 오버로딩되어 있을 경우 이 정보만으로는 원하는 타입을 정확하게 판단하기 어렵다. 또한 생성자를 호출하기 전에 객체를 위한 메모리를 할당해야 하는데 이 시점에서 생성할 객체의 크기를 먼저 계산할 수 있어야 하므로 클래스 이름에 타입이 명시되어야 한다.

함수에서와 마찬가지로 클래스 템플릿도 단순한 선언에 불과하며 컴파일러는 이 템플릿의 모양을 기억해 두었다가 객체가 생성될 때 전달된 타입에 맞는 클래스 정의를 구체화한다. 만약 클래스 템플릿 선언만 있고 객체를 생성하지 않는다면 템플릿은 무시된다. main에서 int, char, double 타입의 PosValue 객체를 각각 선언했는데 이 선언문에 의해 세 개의 클래스가 구체화될 것이다. 확인을 위해 세 개의 객체를 만든 후 OutValue 함수를 호출하여 각 좌표에 값을 출력해 보았다.

템플릿으로부터 만들어지는 클래스도 분명히 클래스이며 일반적인 클래스와 전혀 다를 바가 없다. 템플릿 클래스로부터 상속하는 것도 가능하며 문법도 동일하되 기반 클래스의 이름에 < > 괄호가 사용되는 차이밖에 없다. 다음 클래스는 PosValue<int>로부터 새로운 클래스를 파생한다.

 

class PosValue2 : public PosValue<int> { ... }

 

템플릿 클래스가 다른 클래스의 기반 클래스로 사용되면 컴파일러는 클래스를 즉시 구체화한다. 설사 이 클래스의 인스턴스 선언문이 없더라도 말이다. 템플릿으로부터 만들어지지 않은 일반 클래스의 특정 멤버 함수만 템플릿으로 선언하는 것도 가능하다. 멤버 함수도 분명히 함수이므로 타입에 따라 여러 벌이 필요하다면 원하는 함수 하나만 함수 템플릿으로 만들면 된다. 다음 예제는 그 예를 보여 준다.

 

: TempMember

#include <Turboc.h>

#include <iostream>

using namespace std;

 

class Some

{

private:

     int mem;

 

public:

     Some(int m) : mem(m) { }

     template <typename T>

     void memfunc(T a) {

          cout << "템플릿 인수 = " << a << ", mem = " << mem << endl;

     }

};

 

void main()

{

     Some s(9999);

 

     s.memfunc(1234);

     s.memfunc(1.2345);

     s.memfunc("string");

}

 

Some 클래스에는 함수 템플릿이 하나 포함되어 있으며 이 함수는 임의 타입 T형의 변수 a를 인수로 전달받아 그 값을 화면으로 출력한다. 실제 어떤 멤버 함수가 호출되는가에 따라 클래스 Some의 멤버 함수 개수가 결정될 것이다.