728x90

황기태 저자의 명품 C++ Programming 개정판을 읽고 학습한 내용을 정리한 포스트입니다!

https://search.shopping.naver.com/book/catalog/32436115747

 

C++ Programming : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

제네릭 클래스 개요

  • template를 이용하면 제네릭 클래스(generic class)도 만들 수 있다.
  • int 값을 저장하는 스택, double 값을 저장하는 스택, char 값을 저장하는 스택 등 다양한 스택이 필요할 수 있다.
  • 다만 이들 스택 클래스는 데이터의 타입만 다를 뿐 알고리즘은 동일하기에, 템플릿을 이용하여 스택에 저장되는 데이터 타입을 일반화시킨 제너릭 스택 클래스를 만들어보자.

선언

  • 클래스 선언부와 구현부를 모두 template로 선언한다.
  • 제네릭 클래스의 멤버 함수는 자동 제네릭 함수이다.

 

선언부
  • 스택에서 다루는 데이터 타입은 하나이므로 제네릭 타입은 T 하나만 필요하기에 아래와 같이 선언한다.
  • 같이 저장되는 스택 공간 data []를 T 타입으로 선언하고, push()의 매개 변수와 pop()의 리턴 타입도 T로 선언한다.
template <class T>
class MyStack {
	int tos;// top of stack
	T data [100]; // T 타입의 배열. 스택의 크기는 100
public:
	MyStack();
	void push(T element); // T 타입 원소 element를 data [] 배열에 삽입
	T pop(); //  스택의 탑에 있는 데이터를 data[] 배열에서 리턴
};

 

구현부
  • 멤버 함수를 작성하는 곳
  • 클래스이름을 MyStack 대신 MyStack<T>로 사용하고, 각 멤버 함수 앞에 template <class T>를 붙여 제네릭 함수임을 선언한다.
template <class T1, class T2>
void mcopy(T1 src[], T2 dest[], int n = 5) {
	for (int i = 0; i < n; i++)
		dest[i] = (T2)src[i];
}


template <class T>
void MyStack<T>::push(T element) {
	...
}
template <class T> T MyStack<T>::pop() { // 한 줄에 선언할 수 있음
	...
}

 

구체화

  • 제네릭 클래스를 이용할 때는 클래스의 이름과 함께 제네릭 타입 T에 적용할 구체적인 타입을 지정해야 하낟.
MyStack<int> iStack; // int 타입을 다루는 스택 객체 생성
MyStack<double> dStack; // double 타입을 다루는 스택 객체 생성

 

컴파일러는 아래와 같은 구체화 과정을 진행한다.

  1. MyStack 템플릿의 T에 int나 double을 적용하여 두 개의 구체화된 버전의 C++ 클래스 소스(specialized class)를 생성한다.
  2. 두 C++ 클래스를 컴파일하고 iStack 객체와 dStack 객체를 생성하도록 컴파일한다.
iStack.push(3);
int n = iSTack.pop();

dStack.push(3.5);
double d = dSTack.pop();
  • 위와 같이 보통 객체처럼 사용할수도 있지만 아래와 같이 제네릭 클래스의 포인터를 선언하고 동적으로 객체를 생성할 수도 있다.
MyStack<char> *p = new MyStack<char>();
p->push('a');
char c = p->pop();
delete p;

 

함수의 매개 변수 타입이 제네릭 클래스일 때
  • 함수의 매개 변수 타입이 제네릭 클래스 일 때, 함수의 원형을 선언하는 방법을 알아보자.
void popAll(MyStack<int> s) { ... } // 매개 변수 s: int 타입의 MyStack 객체

MyStack<int> iStack;
popAll(iStack);
  • 앞의 popAll() 함수가 실행되면 iStack 객체를 복사하여 객체 s가 생긴다. 그러나 아래와 같이 참조 매개 변수를 사용하면 객체 복사의 오버헤드를 제거할 수 있다.
void popAll(MyStack<int> &r) { ... } // 매개 변수 r: int 타입의 MyStack 객체의 참조

popAll(iStack);
  • 매개 변수로 제네릭 객체의 포인터도 선언 가능하다.
void popAll(MyStack<int> *p); // 매개 변수 p: int 타입의 MyStack 객체의 포인터

popAll(&iStack);

 

이제 스택 클래스를 일반화한 제네릭 스택 MyStack을 작성하고 실행해보자.

#include <iostream>
using namespace std;

template <class T>
class MyStack {
	int tos;// top of stack
	T data [100]; // T 타입의 배열. 스택의 크기는 100
public:
	MyStack();
	void push(T element); // T 타입 원소 element를 data [] 배열에 삽입
	T pop(); //  스택의 탑에 있는 데이터를 data[] 배열에서 리턴
};

template <class T>
MyStack<T>::MyStack() { // 생성자
	tos = -1; // 스택은 비어 있음
}

template <class T>
void MyStack<T>::push(T element) {
	if(tos == 99) {
		cout << "stack full";
		return;
	}
	tos++;
	data[tos] = element;
}

template <class T> 
T MyStack<T>::pop() {
	T retData;
	if(tos == -1) {
		cout << "stack empty";
		return 0; // 오류 표시
	}
	retData = data[tos--];
	return retData;
}

int main() {
	MyStack<int> iStack; // int 만 저장할 수 있는 정수 스택
	iStack.push(3);
	cout << iStack.pop() << endl;

	MyStack<double> dStack; // double 만 저장할 수 있는 정수 스택
	dStack.push(3.5);
	cout << dStack.pop() << endl;

	MyStack<char> *p = new MyStack<char>(); // char 만 저장할 수 있는 정수 스택
	p->push('a');
	cout << p->pop() << endl;
	delete p;
}

/*
3
3.5
a
*/

 

  • 제네릭 스택의 제네릭 타입을 포인터나 클래스로 구체화해보자..
  • MyStack 제네릭 클래스를 활용하여 int*. Point 객체, Point*. string 문자열 등을 저장하고 사용하는 예이다.
#include <iostream>
#include <string>
using namespace std;

// 위에서 작성한 MyStack 템플릿 클래스 코드가 생략되어있음

class Point {
	int x, y;
public:
	Point(int x=0, int y=0) { this->x = x; this->y = y; }
	void show() { cout << '(' << x << ',' << y << ')' << endl; }
};

int main() {
	MyStack<int *> ipStack; // int* 만을 저장하는 스택
	int *p = new int [3];
	for(int i=0; i<3; i++) p[i] = i*10; // 0, 10, 20으로 초기화
	ipStack.push(p); // 포인터 푸시
	int *q = ipStack.pop(); // 포인터 팝
	for(int i=0; i<3; i++) cout << q[i] << ' '; // 화면 출력
	cout << endl;
	delete [] p;

	MyStack<Point> pointStack; // Point 객체만 저장하는 스택
	Point a(2,3), b;
	pointStack.push(a); // Point 객체 a 푸시. 복사되어 저장 
	b = pointStack.pop(); // Point 객체 팝
	b.show(); // Point 객체 출력

	MyStack<Point*> pStack; // Point* 만 저장하는 스택
	pStack.push(new Point(10,20)); // 동적 생성 Point 객체 푸시. 포인터만 저장 
	Point* pPoint = pStack.pop(); // Point 객체의 포인터 팝
	pPoint->show(); // Point 객체 출력

	MyStack<string> stringStack; //  문자열만 저장하는 스택
	string s="c++";
	stringStack.push(s);
	stringStack.push("java");
	cout << stringStack.pop() << ' ';
	cout << stringStack.pop() << endl;
}

void popAll(MyStack<int> s) { ... } // 매개 변수 s: int 타입의 MyStack 객체

MyStack<int> iStack;
popAll(iStack);

 

두 개 이상의 제네릭 타입을 가진 제네릭 클래스

  • 템플릿을 사용하여 2개 이상의 제네릭 타입을 가진 제네릭 클래스를 만들 수 있다.
template <class T1, class T2, class T3>
#include <iostream>
using namespace std;

template <class T1, class T2> // 두 개의 제네릭 타입 선언
class GClass {
	T1 data1;
	T2 data2;
public:
	GClass();
	void set(T1 a, T2 b);
	void get(T1 &a, T2 &b);
};

template <class T1, class T2> 
GClass<T1, T2>::GClass() { 
	data1 = 0; data2 = 0;
}

template <class T1, class T2>
void GClass<T1, T2>::set(T1 a, T2 b) {
	data1 = a; data2 = b;
}
template <class T1, class T2>
void GClass<T1, T2>::get(T1 & a, T2 & b) {
	a = data1; b = data2;
}

int main() {
	int a;
	double b;
	GClass<int, double> x;
	x.set(2, 0.5); // int, double의 매개 변수
	x.get(a, b);
	cout << "a=" << a << '\t' << "b=" << b << endl;

	char c;
	float d;
	GClass<char, float> y;
	y.set('m', 12.5); // char, double의 매개 변수
	y.get(c, d);
	cout << "c=" << c << '\t' << "d=" << d << endl;
}
728x90

'Programming Language > C++' 카테고리의 다른 글

[C++] auto와 람다식  (0) 2023.12.20
[C++] 표준 템플릿 라이브러리(STL)  (0) 2023.12.20
[C++] 다양한 제네릭 함수 만들기  (0) 2023.12.19
[C++] 템플릿  (0) 2023.12.19
[C++] 추상 클래스  (1) 2023.12.19