본문 바로가기

Programing/C++

[C++] 클래스 템플릿

728x90
반응형

*C++ 열혈 프로그래밍 참조 (윤성우 저)

 

1) 클래스 템플릿

앞서 함수를 템플릿으로 정의 했듯이 클래스도 템플릿으로 정의가 가능하다.

그리고 이렇게 정의된 템플릿을 가리켜 '클래스 템플릿'이라 하며, 이를 기반으로 컴파일러가 만들어내는 클래스를 가리켜 '템플릿 클래스' 라고 한다.

 

클래스 템플릿의 장점은 제공되는 기능과 내부의 행동이 모두 동일한데, 저장의 대상이 다르다는 이유로 클래스를 여러개 정의하는 것을 1개로 정의하게 모두 구현 하게 할수 있다.

예제를 들어보겠다.

#include <iostream>
using namespace std;
template <typename T>
class Point
{
private:
    T xpos, ypos;
public:
    Point( T x= 0, T y = 0) : xpos(x), ypos(y)
    {    }
    void ShowPosition() const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
};

int main()
{
    Point<int> pos1(3,4); //int형 템플릿 클래스 생성
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6); //double형 템플릿 클래스 생성
    pos2.ShowPosition();

    Point<char> pos3('P', 'F'); //char형 템플릿 클래스 생성
    pos3.ShowPosition();
}

 

템플릿 함수를 호출 할때와 가능 했지만, 템플릿 클래스에서는 객체를 생성할 때 <int>,<double>,<char>와 같은 자료형을 생략할 수 없다.

 

2) 클래스 템플릿의 선언와 정의의 분리

클래스 템플릿도 멤버 함수를 클래스 외부에 정의하는것이 가능하다.

 

template <typename T>
class SimpleTemplate
{
public:
    T SimpleFunc(const T& ref);
};

이 함수의 SimpleFunc는 다음과 같이 외부에 정의해야한다.

 

template <typename T>
T SimpleTemplate<T>::SimpleFunc(const T& ref)
{
    // 내용
}

 

위의 함수 정의에서 SimpleTemplate<T>가 의미하는 바는 "T에 대해 템플릿화 된 SimpleTemplate 클래스 템플릿"

이 경우, 클래스 템플릿의 함수의 정의가 완전히 별개이기 때문에 각각에 대해서 문자 T가 무엇을 의미해야하는지 설명이 되어야 한다.

이를 반영해서 앞에 나온 예제에서 함수의 정의를 분리해보면,

 

 

#include <iostream>
using namespace std;
template <typename T>
class Point
{
private:
    T xpos, ypos;
public:
    Point( T x= 0, T y = 0); 
    // : xpos(x), ypos(y)
    // {    }
    void ShowPosition() const
    // {
    //     cout << '[' << xpos << ", " << ypos << ']' << endl;
    // }
};

template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y)
{ }

template <typename T>
void Point<T>::ShowPosition() const
{ 
    cout << '[' << xpos << ", " << ypos << ']' << endl;
}

int main()
{
    Point<int> pos1(3,4); //int형 템플릿 클래스 생성
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6); //double형 템플릿 클래스 생성
    pos2.ShowPosition();

    Point<char> pos3('P', 'F'); //char형 템플릿 클래스 생성
    pos3.ShowPosition();
}

 

다음은 파일 분할 원칙을 적용하여, 파일을 분할해서 컴파일 및 실행 해보겠다.

 

1) 헤더 파일에 클래스 템플릿을 정의한다.

#PointTemplate.h

#ifndef __POINT_TEMPLATE_H_
#define __POINT_TEMPLATE_H_

template <typename T>
class Point
{
private:
    T xpos, ypos;
public:
    Point( T x= 0, T y = 0); 
    void ShowPosition() const
};
#endif

2) 소스파일에 생성자와 멤버 함수를 정의한다.

#PointTemplate.cpp

#include <iostream>
#include "PointTemplate.h"
using namespace std;

template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y)
{ }

template <typename T>
void Point<T>::ShowPosition() const
{ 
    cout << '[' << xpos << ", " << ypos << ']' << endl;
}

마지막으로 main함수파일을 정의 한다.

#include <iostream>
#include "PointTemplate.h"
using namespace std;

int main()
{
    Point<int> pos1(3,4);
    pos1.ShowPosition();

    Point<double> pos2(2.4, 3.6);
    pos2.ShowPosition();

    Point<char> pos3('P', 'F');
    pos3.ShowPosition();
}

일반적으로 이렇게 정의를 하지만 컴파일을 하면 에러가 난다.

이유는 main함수가 정의된 소스파일 PointMain.cpp가 컴파일 될 때, 컴파일러는 총 3개의 템플릿 클래스를 생성해야 한다. 그리고 이를 위해서는 클래스 템플릿인 Point의 모든 것을 알아야한다. 

즉, 컴파일러에는 헤더파일(PointTemplate.h)에 담긴 정보 뿐만 아니라, 소스파일(PointTemplate.cpp)에 담긴 정보도 필요하다.

그런데 메인 파일에는 헤더파일만 정의가 되어 있기 때문에 소스파일에 대한 정보가 없어 클래스 템플릿인 Point에 대한 정보가 부족하여

컴파일 에러를 발생시키는 것이다.

 

이를 해결하기 위해서는

1) 헤더파일에 생서자, 멤버함수의 정의를 모두 넣는다

2) Main파일에 #include "PointTemplate.cpp" 구문을 추가한다.

 

이렇게 하면 정상적으로 컴파일이 가능해진다.

 

배열 클래스의 템플릿화

 

이제 이전에 동일한 기능의 배열 클래스를 자료형에 따라 여러개의 배열클래스를 정의했던 것을, 템플릿을 이용하여 하나의 클래스 템플릿 정의로 대체하는 예제를 구현해보겠다

먼저 헤더 파일 구현,

클래스 템플릿 BoundCheckArray의 모든것을 담아 두었다. 따라서 이 헤더파일 하나만 포함을 하면 BoundCheckArray템플릿 기반의 객체를 생성할 수 있다.

#ifndef __ARRAY_TEMPLATE_H_
#define __ARRAY_TEMPLATE_H_

#include <iostream>
#include <cstdlib>
using namespace std;

template<typename T>
class BoundCheckArray
{

private:
    T * arr;
    int arrlen;
    BoundCheckArray(const BoundCheckArray& arr) { }
    BoundCheckArray& operator=(const BoundCheckArray& arr) { }
public:
    BoundCheckArray(int len);
    T& operator[] (int idx);
    T operator[] (int idx) const;
    int GetArrlen() const;
    ~BoundCheckArray();
};

template <typename T>
BoundCheckArray<T>::BoundCheckArray(int len) : arrlen(len)
{
    arr = new T[len];
}

template <typename T>
T& BoundCheckArray<T>::operator[] (int idx)
{
    if(idx < 0 || idx >= arrlen)
    {
        cout << "Array index out of bound exception" << endl;
        exit(1);
    }
    return arr[idx];
}

template <typename T>
T BoundCheckArray<T>::operator[] (int idx) const
{
    if(idx < 0 || idx >= arrlen)
    {
        cout << "Array index out of bound exception" << endl;
        exit(1);
    }
    return arr[idx];
}

template <typename T>
int BoundCheckArray<T>::GetArrlen() const{
    return arrlen;
}

template<typename T>
BoundCheckArray<T>::~BoundCheckArray()
{
    delete []arr;
}

#endif

BoundCheckArray<Point> 객체 생성을 위해서 Point 클래스를 정의하였다. 

#ifndef __POINT_H_
#define __POINT_H_

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x = 0, int y = 0);
    friend ostream& operator<<(ostream& os, const Point& pos);
};
#endif

main함수를 정의한다.

#include <iostream>
#include "ArrayTemplate.h"
#include "Point.h"
using namespace std;

int main()
{
    /*** int 형 정수 저장 ***/
    BoundCheckArray<int> iarr(5);
    for(int i =0; i < 5; i++)
        iarr[i] = (i+1)*11;
    for(int i =0; i<5; i++)
        cout<<iarr[i]<<endl;
    
    /*** Point 객체 저장 ***/
    BoundCheckArray<Point> oarr(3);
    oarr[0] = Point(3,4);
    oarr[1] = Point(5,6);
    oarr[2] = Point(7,8);
    for(int i =0; i<oarr.GetArrlen(); i++)
        cout<<oarr[i];
    
    /*** Point 객체의 주소 값 저장 ***/
    typedef Point* POINT_PTR;
    BoundCheckArray<POINT_PTR> parr(3);
    parr[0] = new Point(3, 4);
    parr[1] = new Point(5, 6);
    parr[2] = new Point(7, 8);
    for (int i = 0; i<parr.GetArrlen(); i++)
        cout <<*(parr[i]);

    delete parr[0];
    delete parr[1];
    delete parr[2];
}

실행 결과

11

22

33

44

55

[3, 4]

[5, 6]

[7, 8]

[3, 4]

[5, 6]

[7, 8]

반응형