스물일곱번째 열혈강의
이제 마지막을 정리하는 단계별 프로젝트 8단계를 진행하도록 하자.
우리가 원하는건 대입연산자 오버로딩으로 깊은복사의 진행이 주체가 될 것이다.
또한, 배열이 멤버로 선언되어 객체의 저장을 하는 AccountHandler
도 이전에 블로깅하며 만들었던 BoundCheckPointPtrArray
를 사용해 보도록 하자.
먼저 배열 클래스가 선언된 AccountArray.h
와 AccountArray.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)
와 같다는 것에서 볼 수 있듯이, 소괄호를 묶는 형태로 형 변환을 명령할 수 있다.
이제 이 이후의 함수 템플릿의 특수화 부터는 다음 포스팅에 진행하자.