문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C++ - 클래스 템플릿(Class Template) (클래스 템플릿의 정의, 클래스 템플릿의 선언과 정의의 분리, 배열 클래스의 템플릿화)

벼리네 2021. 6. 29. 17:11
반응형

[문과 코린이의 IT 기록장] C++ - 클래스 템플릿(Class Template) (클래스 템플릿의 정의, 클래스 템플릿의 선언과 정의의 분리, 배열 클래스의 템플릿화)

[문과 코린이의 IT 기록장] C++ - 클래스 템플릿(Class Template) (클래스 템플릿의 정의, 클래스 템플릿의 선언과 정의의 분리, 배열 클래스의 템플릿화)


1. 클래스 템플릿의 정의

 

[문과 코린이의 IT 기록장] C,C++ - 템플릿(Template)에 대한 이해와 함수 템플릿 (함수를 대상으로 템

[문과 코린이의 IT 기록장] C,C++ - 템플릿(Template)에 대한 이해와 함수 템플릿 (함수를 대상으로 템플릿 이해하기, 함수 템플릿과 템플릿 함수, 둘 이상의 형(Type)에 대해 템플릿 

vansoft1215.tistory.com

 * 이전의 내용처럼 함수를 템플릿으로 정의했듯이, 클래스도 템플릿으로 정의가 가능하다.

 


 

[문과 코린이의 IT 기록장] C,C++ - 연산자 오버로딩 5 : 배열의 인덱스 연산자 오버로딩 (배열 클래

[문과 코린이의 IT 기록장] C,C++ - 연산자 오버로딩 4 : 배열의 인덱스 연산자 오버로딩 (배열 클래스,  const 함수를 이용한 오버로딩의 활용, 객체의 저장을 위한, 배열 클래스의

vansoft1215.tistory.com

- 위 포스팅에서 다음과 같은 클래스를 정의한 바 있다.

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;
}

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형