read

스물일곱번째 열혈강의


guardiola

이제 마지막을 정리하는 단계별 프로젝트 8단계를 진행하도록 하자.

우리가 원하는건 대입연산자 오버로딩으로 깊은복사의 진행이 주체가 될 것이다.

또한, 배열이 멤버로 선언되어 객체의 저장을 하는 AccountHandler도 이전에 블로깅하며 만들었던 BoundCheckPointPtrArray를 사용해 보도록 하자.

먼저 배열 클래스가 선언된 AccountArray.hAccountArray.cpp파일을 새로이 만들것이고, Account.h, Account.cpp, AccountHandler.h를 수정 할 것이다.


/* Account.h */
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__

class Account
{
private:
	int accID;
	int balance;    
	char * cusName;
public:
	Account(int ID, int money, char * name);
	Account(const Account & ref);
	Account& operator=(const Account& ref);

	int GetAccID() const;
	virtual void Deposit(int money);
	int Withdraw(int money) ;
	void ShowAccInfo() const ;
	~Account();
};
#endif

/* Account.cpp */
#include "BankingCommonDecl.h"
#include "Account.h"

Account::Account(int ID, int money, char * name)
	: accID(ID), balance(money)
{
	cusName=new char[strlen(name)+1];
	strcpy(cusName, name);
}

Account::Account(const Account & ref)
	: accID(ref.accID), balance(ref.balance)
{
	cusName=new char[strlen(ref.cusName)+1];
	strcpy(cusName, ref.cusName);
}

Account& Account::operator=(const Account& ref)
{
	accID=ref.accID;
	balance=ref.balance;

	delete []cusName;
	cusName=new char[strlen(ref.cusName)+1];
	strcpy(cusName, ref.cusName);
	return * this;
}

int Account::GetAccID() const { return accID; }

void Account::Deposit(int money)
{
	balance+=money;
}

int Account::Withdraw(int money)
{
	if(balance<money)
		return 0;

	balance-=money;
	return money;
}

void Account::ShowAccInfo() const
{
	cout<<"계좌ID: "<<accID<<endl;
	cout<<"이 름: "<<cusName<<endl;
	cout<<"잔 액: "<<balance<<endl;
}

Account::~Account()
{
	delete []cusName;
}

/* AccountHandler.h */
#ifndef __ACCOUN_HANDLER_H__
#define __ACCOUN_HANDLER_H__

#include "Account.h"
#include "AccountArray.h"

class AccountHandler
{
private:
	BoundCheckAccountPtrArray accArr;
	int accNum;
public:
	AccountHandler();
	void ShowMenu(void) const;
	void MakeAccount(void);
	void DepositMoney(void);
	void WithdrawMoney(void);
	void ShowAllAccInfo(void) const;
	~AccountHandler();
protected:
	void MakeNormalAccount(void);
	void MakeCreditAccount(void);
};
#endif

/* AccountArray.h */
#ifndef __ACCOUN_ARRAY_H__
#define __ACCOUN_ARRAY_H__

#include "Account.h"

typedef Account * ACCOUNT_PTR;

class BoundCheckAccountPtrArray
{
private:
	ACCOUNT_PTR * arr;
	int arrlen;

	BoundCheckAccountPtrArray(const BoundCheckAccountPtrArray& arr) { }
	BoundCheckAccountPtrArray& operator=(const BoundCheckAccountPtrArray& arr) { }

public:
	BoundCheckAccountPtrArray(int len=100);
	ACCOUNT_PTR& operator[] (int idx);
	ACCOUNT_PTR operator[] (int idx) const;
	int GetArrLen() const;
	~BoundCheckAccountPtrArray();
};
#endif

/* AccountArray.cpp */
#include "BankingCommonDecl.h"
#include "AccountArray.h"

BoundCheckAccountPtrArray::BoundCheckAccountPtrArray(int len) :arrlen(len)
{
	arr=new ACCOUNT_PTR[len];
}

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

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

int BoundCheckAccountPtrArray::GetArrLen() const
{
	return arrlen;
}

BoundCheckAccountPtrArray::~BoundCheckAccountPtrArray()
{
	delete []arr;
}


템플릿


사실 이 다음은 String에 대해 적어야 하는데, 현재 이 도서는 개정 전 상태로 적혔기 때문에 적지 않는 것이 더 낫다는 생각이 들어 템플릿으로 바로 넘어왔다. 현재, std에 대한 내용은 이 블로그 를 참고 하도록 하자.

탬플릿 은 모형자라는 뜻이다. 즉, 모형을 그릴 때 사용하며, 색을 나의 볼팬 색이 뭐냐에 따라 달리 그려지게 된다.

그럼 이걸 그대로 함수 템플릿 에 대해 적용해보자.

함수 템플릿은 함수를 만들어 낸다. 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야 한다.

그럼 아주 재밌는 개념이 된다. 바로 함수를 만들어 낸다 는 것이다.

즉, 함수 템플릿은 함수를 만드는 도구 가 된다. 또한, 모형자에 볼펜관계처럼 다양한 자료형의 함수 를 만들어 낼 수 있다.

그럼 간단히 구현해 보도록 하자.


int Add(int num1, int num2){
  return num1 + num2;
}

이 함수의 기능과 자료형은 다음과 같다.

  • 함수의 기능 -> 덧셈
  • 대상 자료형 -> int형 데이터

이러한 함수를 만들어 낼 수 있는 템플릿은 다음처럼 정의한다.


T Add(T num1, T num2){
  return num1 + num2;
}

이를 앞서 정의한 함수와 비교하면, int형을 T로 대신했다는 게 보이는데, 이는 자료형을 결정짓지 않은, 그래서 나중에 T를 대신해서 실제 자료형을 결정하겠다는 것이다.

즉, 다음과 같다.

  • 함수의 기능 -> 덧셈
  • 대상 자료형 -> 결정되어 있지 않음

그런데 이게 끝이 아니다. 컴파일러는 다음과 같은 내용을 알아야 하는데, T는 자료형을 결정짓지 않겠다는 의미로 사용한 것이다. 즉, 함수를 만들어 내는 템플릿을 정의하기 위해 사용된 것이라는 메세지를 가져야 한다.


template <typename T>
T Add(T num1, T num2){
  return num1 + num2;
}

위의 템플릿 정의에 다음 문장이 있다.

template <typename T>

이는 T라는 이름을 이용해 아래의 함수를 템플릿으로 정의한다는 뜻이다. 추가로, template <class T>라고 사용할 수도 있다.

그럼 이걸 가지고 함수를 만들어보자.

  • int 형 덧셈을 진행하는 Add 함수
  • double 형 덧셈을 진행하는 Add 함수

위 두가지를 만들 것인데, 미리 만들어두지 않아도 된다. 컴파일러가 함수의 호출문장으로 만들어 내기 때문이다. 예제를 보자.


#include <iostream>

using namespace std;

template <typename T>
T Add(T num1, T num2){
    return num1 + num2;
}

int main(){
    cout << Add<int>(15, 20) << endl;
    cout << Add<double>(2.9, 3.7) << endl;
    cout << Add<int>(3.2, 3.2) << endl;
    cout << Add<double>(3.14, 2.75) << endl;
    return 0;
}

아마 직관적으로 이해가 가능할 것이다.

  • T가 int인 경우 -> Add 함수
  • T가 double인 경우 -> Add

즉, 위의 에제에서 함수 템플릿을 기반으로 만들어진 함수는 다음과 같이 표현하는 것이 더 정확하다.


int Add<int>(int num1, int num2){
  return num1 + num2;
}

double Add<double>(double num1, double num2){
  return num1 + num2;
}

그럼 두가지만 알고 넘어가자.

함수를 템플릿으로 정의하면, 매 호출문장마다 함수를 만들 것인가

그렇지 않다. 한번 만들어지면 만들어진 함수를 호출하고 새로 만들지 않는다. 득, 자료형 당 하나만 가진다.

컴파일 할 때 함수가 만들어지면, 속도는 느린가

물론 느려지기는 하다. 근데 컴파일의 속도가 느려지지 실행속도가 느려지지 않는다.

또한, 호출하기가 조금 불편하다고 느낄 수 있다. 그러나 다음과 같이 고쳐도 된다.


int main(){
    cout << Add(15, 20) << endl;
    cout << Add(2.9, 3.7) << endl;
    cout << Add(3.2, 3.2) << endl;
    cout << Add(3.14, 2.75) << endl;
    return 0;
}

즉, 컴파일러가 자료형을 판단하므로 가능해진다. 전달되는 인자의 자료형이 double 형이라면, T를 double로 바꿔준다.

따라서 3번째 문장은 double로 인식해, 그 위의 예제와는 출력 차이가 있다.


함수 템플릿과 템플릿 함수


앞서 한 것들은 함수 템플릿이다.

그에 반해, 템플릿을 기반으로 컴파일러가 만들어 내는 다음 유형의 함수들을 템플릿 함수라고 한다.


int Add<int>(int num1, int num2){
  return num1 + num2;
}

double Add<double>(double num1, double num2){
  return num1 + num2;
}

두 단어가 헷갈릴 수 있지만 잘 생각해서 기억하도록 하자.

함수를 만드는 데 사용되는 템플릿, 템플릿을 기반으로 만들어진 함수 로 각각 연결지을 수 있을 것이다.

여기서 템플릿 함수는 템플릿을 기반으로 만들어진, 호출이 가능한 함수다. 그러나 일반함수와 구분이 되며 두가지 모두 함께 존재할 수 있다. 다음 예제를 보자.


#include <iostream>

using namespace std;

template <typename T>
T Add(T num1, T num2){
    cout << "T Add(T num1, T num2)" << endl;
    return num1 + num2;
}

int Add(int num1, int num2){
    cout << "Add(int num1, int num2)" << endl;
    return num1 + num2;
}

double Add(double num1, double num2){
    cout << "Add(double num1, double num2)" << endl;
    return num1 + num2;
}

int main(){
    cout << Add(5, 7) << endl;
    cout << Add(3.7, 7.5) << endl;
    cout << Add<int>(5, 7) << endl;
    cout << Add<double>(3.7, 7.5) << endl;
    return 0;
}

결과를 본다면 어떻게 출력이 되는지 알 수 있을 것이다.

물론, 템플릿을 정의한 상태에서 또다시 일반함수를 정의하는 것은 좋지 못하다. 단지 컴파일러에 의해 만들어지는 템플릿 함수가 일반함수와 구분된다는 점을 알고 가도록 하자.

참고로, 템플릿 함수는 컴파일러에 의해서 생성된 함수이기 때문에 생성된 함수(Generated Function)라고 하고, 나중에 알아볼 템플릿 클래스역시 생성된 클래스(Generated Class)라고도 불린다.


둘 이상의 형(Type)에 대해 템플릿 선언하기


함수 템플릿을 정의할 때 기본 자료형도 선언할 수 있으며, 둘 이상의 타입에 대해서도 선언이 가능하다.


#include <iostream>

using namespace std;

template <class T1, class T2>
void ShowData(double num){
    cout << (T1)num << ", " << (T2)num << endl;
};

int main(){
    ShowData<char, int>(65);
    ShowData<char, int>(67);
    ShowData<char, double>(68.9);
    ShowData<short, double>(69.2);
    ShowData<short, double>(70.4);
    return 0;
}

앞서 설명했듯이, class라고 선언된 부분은 typename으로 바꿔도 된다.

또한, cout << (T1)num << ", " << (T2)num << endl; 이 문장은

cout << T1(num) << ", " << T2(num) << endl;으로 대신할 수 있다.

int num = (int)3.14 역시, int num = int(3.14) 와 같다는 것에서 볼 수 있듯이, 소괄호를 묶는 형태로 형 변환을 명령할 수 있다.

이제 이 이후의 함수 템플릿의 특수화 부터는 다음 포스팅에 진행하자.

Blog Logo

구찌


Published

Image

구찌의 나도한번 해블로그

구찌의 개발 블로그 입니다.

Back to Overview