문과 코린이의, [C. C++] 기록/C++ 이론
[문과 코린이의 IT 기록장] C++ - 클래스 템플릿(Class Template) (클래스 템플릿의 정의, 클래스 템플릿의 선언과 정의의 분리, 배열 클래스의 템플릿화)
벼리네
2021. 6. 29. 17:11
반응형
[문과 코린이의 IT 기록장] C++ - 클래스 템플릿(Class Template) (클래스 템플릿의 정의, 클래스 템플릿의 선언과 정의의 분리, 배열 클래스의 템플릿화)
1. 클래스 템플릿의 정의
* 이전의 내용처럼 함수를 템플릿으로 정의했듯이, 클래스도 템플릿으로 정의가 가능하다.
- 위 포스팅에서 다음과 같은 클래스를 정의한 바 있다.
class BoundCheckIntArray { ... };
class BoundCheckPointArray { ... };
class BoundCheckPointPtrArray { ... };
이들은 모두 배열 클래스들이다. 그러나, 저장의 대상이 달라 세 개나 따로 정의할 수밖에 없었다.
- 이럴 때는 클래스 템플릿을 정의하여 문제를 해결할 수 있다.
ex 1 ) 기존 Point 클래스
class Point {
private:
int xpos, ypos;
public:
Point(int x = 0, int y = 0) : xpos(x), ypos(y) { }
void ShowPosition() const {
// const 변수를 함수 내부에서 사용할 수 있지만, 값에 대한 변경은 방지한다.
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
};
ex 2 ) Point 클래스를 클레스 탬플릿으로 변환
template <typename T>
class Point {
private:
T xpos, ypos;
public:
Point(T x = 0, T y = 0) : xpos(x), ypos(y) { }
void ShowPosition() const {
// const 변수를 함수 내부에서 사용할 수 있지만, 값에 대한 변경은 방지한다.
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
};
* 클래스 템플릿의 정의방법은 함수 템플릿의 정의방법과 동일하다.
Case 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 {
// const 변수를 함수 내부에서 사용할 수 있지만, 값에 대한 변경은 방지한다.
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
};
int main() {
Point <int>pos1(3, 4); // T를 <int>로 하여 만든 템플릿 클래스
pos1.ShowPosition();
Point <double>pos2(2.4, 3.6); // T를 <double>로 하여 만든 템플릿 클래스
pos2.ShowPosition();
// 좌표정보를 문자로 표시하는 상황의 표현
Point <char> pos3('P', 'F'); // T를 <char>로 하여 만든 템플릿 클래스
pos3.ShowPosition();
return 0;
}
- 함수 템플릿과 마찬가지로, '클래스 템플릿'을 기반으로 '템플릿 클래스'를 만들어낸다.
Point <int> 템플릿 클래스
Point <double> 템플릿 클래스
Point <char> 템플릿 클래스
- 템플릿 클래스는 템플릿 함수와 달리, 객체 생성 시 자료형 정보(ex. <int>, <double>)를 꼭 명시해주어야 한다.
2. 클래스 템플릿의 선언과 정의의 분리
- 클래스 템플릿도 맴버함수를 클래스 외부에 정의하는 것이 가능하다.
ex )
template <typename T>
class SimpleTemplate
{
public:
T SimpleFunc(const T& ref);
}
(중략)
template <typename T> // 꼭 써줘야 함
T SimpleTemplate<T> :: SimpleFunc(const T ref) { ... }
SimpleTemplate<T> : T에 대해 템플릿화 된, SimpleTemplate 클래스 템플릿
Case 2 )
#include<iostream>
using namespace std;
template <typename T>
class Point {
private:
T xpos, ypos;
public:
Point(T x = 0, T y = 0);
void ShowPosition() const;
};
template <typename T>
Point<T>::Point(T x, T y):xpos(x), ypos(y) { }
// T에 대해 템플릿화 된, Point 클래스 템플릿
template <typename T>
void Point<T> :: ShowPosition() const {
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
// T에 대해 템플릿화 된, Point 클래스 템플릿
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();
return 0;
}
3. 배열 클래스의 템플릿화
[ ArrayTemplate. H ]
#pragma once
#ifndef __ ARRAY_TEMPLATE_H_
#define __ ARRAY_TEMPLATE_H_
#include<iostream>
#include<cstdlib>
template <typename T>
class BoundCheckArray
{
private:
T* arr;
int arrlen;
/* 깊은 복사가 진행될 수 있도록, 복사생성자와 대입연산자를 private 맴버로 두면서 복사와 대입을 막는다.*/
// 배열은 저장소의 일종이며, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 대부분의 경우 배열에서의 복사는 불필요하거나 잘못된 일로 간주하곤 한다.
BoundCheckArray(const BoundCheckArray& arr) { } // 복사생성자
BoundCheckArray& operator=(const BoundCheckArray& arr) { } // 대입연산자
public:
BoundCheckArray(int len); // 생성자
T& operator[] (int idx); // 배열 연산자
T operator[](int idx)const; // const 배열 연산자
int GetArrLen() const; // arrlen : 배열의 길이 반환 함수
~BoundCheckArray(); // 소멸자
};
#endif
[ ArrayTemplate.cpp ]
#include "ArrayTemplate.h"
#include<iostream>
using namespace std;
template<typename T>
BoundCheckArray<T>::BoundCheckArray(int len) :arrlen(len) { // 생성자
arr = new T[len]; // 힙에 배열만큼의 메모리 공간 마련
}
template<typename T>
T& BoundCheckArray<T>::operator[] (int idx) { // 배열 연산자
/* iarr[i] = iarr.operator[](i) */
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 { // const 맴버함수 추가 배열 연산자
// const 매개변수가 있는 함수가 사용될 예정인 경우, operator[]함수 또한 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; // 배열 메모리 해제
}
[ Point.h ]
#pragma once
#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); // Point 객체의, cout 연산을 위해
};
#endif
[ Point.cpp ]
#include "Point.h"
Point::Point(int x, int y) :xpos(x), ypos(y) { }
ostream& operator<<(ostream& os, const Point& pos) {
os << '[' << pos.xpos << ", " << pos.ypos << ']' << endl;
return os;
}
[ Main ]
#include <iostream>
#include "Point.h"
#include "ArrayTemplate.h"
#include "ArrayTemplate.cpp"
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;
// POINT_PTR을 *Point 자료형의 별칭으로 정의한다.
// 즉 Point 포인터 형 = POINT_PTR
// 저장의 대상, 또는 연산의 주 대상이 포인터인 경우, 이렇듯 별도의 자료형을 정의하는 것이 좋다.
BoundCheckArray<POINT_PTR> parr(3); // parr은 포인터의 포인터
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];
return 0;
}
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |
반응형