Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C++] 클래스 템플릿(Class Template) 본문

Programming/C++

[Programming/C++] 클래스 템플릿(Class Template)

scii 2021. 2. 7. 00:57

클래스 템플릿의 정의방법은 함수 템플릿의 정의방법과 동일하다. 

#include <iostream>

#pragma warning(disable: 4996)

using std::cout;
using std::cin;
using std::endl;

template <typename T>
class Point {
private:
    T xpos, ypos;

public:
    explicit Point(const T x = 0, const T y = 0) : xpos(x), ypos(y) {}

    void ShowPosition() const {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
};

 int main(const int argc, const char* const argv[]) { 
     Point<int> pos1(5, 7);
     pos1.ShowPosition();

     Point<double> pos2(5.4, 7.6);
     pos2.ShowPosition();

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

     return 0;
}

/* 결과

[5, 7]
[5.4, 7.6]
[P, F]

*/

함수 템플릿과 마찬가지로, 컴파일러는 '클래스 템플릿'을 기반으로 '템플릿 클래스'를 만들어낸다. 위 예제의 경우 총 3개의 템플릿 클래스가 만들어지며, 이들 각각은 다음과 같이 표현을 해서 일반 클래스와 구분을 짓는다.

  • Point<int> 템플릿 클래스
  • Point<double> 템플릿 클래스
  • Point<char> 템플릿 클래스
함수와는 다르게 클래스 템플릿 기반의 객체생성에는 반드시 자료형 정보를 명시해야 한다.

클래스 템플릿의 선언과 정의의 분리

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

template <typename T>
class Point {
private:
    T xpos, ypos;

public:
    explicit Point(const T x = 0, const T y = 0);
    void ShowPosition() const;
};

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

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

함수정의에서 Point<T>가 의미하는 것은 "T에 대해 템플릿화 된 Point 클래스 템플릿" 이라는 의미이다.
그리고 클래스 템플릿의 정의와 함수의 정의가 완전히 별개이기 때문에 각각에 대해서 문자 T가 무엇을 의미하는지 설명해야 한다. 


파일 분할 시 고려 사항

일반적으로 적용하는 파일의 분할원칙을 적용하여, 파일을 분할해서 컴파일 및 실행해보자.

Point.h 헤더파일에는 클래스 템플릿이 정의되어 있다.

// Point.h

#pragma once

template <typename T>
class Point {
private:
    T xpos, ypos;

public:
    explicit Point(const T x = 0, const T y = 0);
    void ShowPosition() const;
};

Point.cpp 소스파일에는 클래스 템플릿 Point의 생성자와 멤버함수가 정의되어 있다.

// Point.cpp

#include <iostream>
#include "Point.h"

using namespace std;

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

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

일반적인 클래스의 선언과 정의를 각각 헤더파일과 소스파일에 나누어 담는 것과 차이가 없다.

main() 함수가 존재하는 파일은 다음과 같이 정의되어 있다.

// PointMain.cpp

#include <iostream>
#include "Point.h"

 int main(const int argc, const char* const argv[]) { 
     Point<int> pos1(5, 7);
     pos1.ShowPosition();

     Point<double> pos2(5.4, 7.6);
     pos2.ShowPosition();

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

     return 0;
}

그런데, 컴파일을 진행하면 문제가 발생한다.  그 이유는 다음과 같다.

컴파일은 파일단위로 이뤄진다. 아래의 코드를 살펴보자.

int main(void) {
  // Point<int> 템플릿 클래스가 만들어지는 시점
  Point<int> pos1(5, 7); 
  
  // Point<double> 템플릿 클래스가 만들어지는 시점
  Point<double> pos2(3.3, 4.4);
  
  // Point<char> 템플릿 클래스가 만들어지는 시점
  Point<char> pos3('P', 'F');
}

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

즉, 컴파일러에는 헤더파일 PointTemplate.h에 담긴 정보뿐만 아니라, PointTemplate.cpp에 담긴 정보도 필요하다. 그런데 main() 함수가 정의된 소스파일에는 PointTemplate.cpp에 담긴 정보를 참조할만한 어떠한 선언도 되어있지 않다. 그래서 컴파일 에러가 발생하는 것이다.

파일단위 컴파일 원칙에 의해서 PointMain.cpp를 컴파일하면서 Point.cpp의 내용을 참조하지 않으며, Point.cpp를 컴파일 할 때에도 PointMain.cpp의 내용을 참조하지 않는다. 따라서 컴파일 에러가 발생한다.

가장 기본적인 해결책은 다음과 같다.

"헤더파일 PointTemplate.h에 템플릿 Point의 생성자와 멤버함수의 정의를 모두 넣는다."

그리고 이것이 싫다면, PointMain.cpp에 다음과 같이 #include문을 하나 더 추가해야 한다.

#include "Point.cpp"

소스파일을 #include 문으로 포함시키니 다소 이상하게 보일 수 있지만, 템플릿의 경우에는 이러한 방법을 사용해서라도 템플릿의 모든 정보를 소스파일에 전달해야 한다. 그리고 #include문의 삽입 하나로 컴파일이 가능해진다.


클래스 템플릿을 사용한 예제

#include <iostream>

#pragma warning(disable: 4996)

using std::cout;
using std::cin;
using std::endl;


template <typename T>
class Point {
private:
    T xpos, ypos;

public:
    explicit Point(const T x = 0, const T y = 0) : xpos(x), ypos(y) {}

    friend std::ostream& operator<<(std::ostream& os, const Point<int>& ref);
    friend std::ostream& operator<<(std::ostream& os, const Point<int>* ref);
};


std::ostream& operator<<(std::ostream& os, const Point<int>& ref) {
    os << '[' << ref.xpos << ", " << ref.ypos << ']';
    return os;
}

std::ostream& operator<<(std::ostream& os, const Point<int>* ref) {
    os << '[' << (*ref).xpos << ", " << (*ref).ypos << ']';
    return os;
}


template <typename T>
class BoundCheckArray {
private:
    T* arr;
    int arrlen;
    explicit BoundCheckArray(const BoundCheckArray& ref) {}
    BoundCheckArray& operator=(const BoundCheckArray& ref) {}

public:
    explicit BoundCheckArray(const int len);
    ~BoundCheckArray();
    T& operator[](const int idx);
    T operator[](const int idx) const;
    int GetArrLen() const;
    void ShowData(void) const;
};

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

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

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

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

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

template <typename T>
void BoundCheckArray<T>::ShowData(void) const {
     for (int i = 0; i < arrlen; i++) {
         cout << arr[i] << endl;
     }
     cout << endl;
}


 int main(const int argc, const char* const argv[]) { 
     /* int형 정수 저장 */
     BoundCheckArray<int> iarr(5);
     for (int i = 0; i < 5; i++)
         iarr[i] = (i + 1) * 10;
     cout << "- int형 정수 -" << endl;
     iarr.ShowData();

     /* Point 객체 저장 */
     BoundCheckArray<Point<int>> oarr(3);
     oarr[0] = Point<int>(1, 2);
     oarr[1] = Point<int>(3, 4);
     oarr[2] = Point<int>(5, 6);
     cout << "- Point 객체 -" << endl;
     oarr.ShowData();

     /* Point 객체의 주소 값 저장 */
     typedef Point<int>* POINT_INT_PTR;
     BoundCheckArray<POINT_INT_PTR> parr(3);
     parr[0] = new Point<int>(10, 20);
     parr[1] = new Point<int>(30, 40);
     parr[2] = new Point<int>(50, 60);
     cout << "- Point* 객체 -" << endl;
     parr.ShowData();

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

     return 0;
}

/* 결과

- int형 정수 -
10
20
30
40
50

- Point 객체 -
[1, 2]
[3, 4]
[5, 6]

- Point* 객체 -
[10, 20]
[30, 40]
[50, 60]

*/
Comments