[C++] 스터디 CPPALOM 6주차: 열혈 C++ 프로그래밍 Chap 13-16
(파워 포인트 파일(.pptx)을 Markdown으로 변환하여 업로드하였음)
# <br>CPPALOM
# <br>6주차 - 열혈 C++ 프로그래밍 Chap 13~16
한수빈
# <br>함수 템플릿
* 함수 템플릿은 함수를 만들어 낸다.
* 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야 한다.
* 이러한 정의를 함수 템플릿(function template)이라 한다.
* 그리고 이러한 정의로 컴파일 시기에 생성된 함수들을 템플릿 함수(template function)이라 한다.
```C++
template <typename T>
T Add(T num1, T num2)
{
return num1+num2;
}
int main(void)
{
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;
}
```
# <br>둘 이상의 타입을 지닌 템플릿 함수
typename 키워드 대신 class를 활용할 수 있다.
쉼표를 이용해서 둘 이상의 템플릿 타입을 명시할 수 있다.
```C++
template <class T1, class T2>
void ShowData(double num)
{
cout<<(T1)num<<", "<<(T2)num<<endl;
}
int main(void)
{
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;
}
```
```C++
A, 65
C, 67
D, 68.9
69, 69.2
70, 70.4
```
# <br>함수 템플릿의 특수화
* 다음 코드의 경우 결함이 존재한다:
* 문자열의 비교는 의도된 동작을 하지 않는다.
```C++
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b ;
}
int main(void)
{
cout<< Max(11, 15) <<endl;
cout<< Max('T', 'Q') <<endl;
cout<< Max(3.5, 7.5) <<endl;
cout<< Max("Simple", "Best") <<endl;
return 0;
}
```
```C++
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b ;
}
template <>
char* Max(char* a, char* b)
{
cout<<"char* Max<char*>(char* a, char* b)"<<endl;
return strlen(a) > strlen(b) ? a : b ;
}
template <>
const char* Max(const char* a, const char* b)
{
cout<<"const char* Max<const char*>(const char* a, const char* b)"<<endl;
return strcmp(a, b) > 0 ? a : b ;
}
int main(void)
{ cout<< Max(11, 15) <<endl;
cout<< Max('T', 'Q') <<endl;
cout<< Max(3.5, 7.5) <<endl;
cout<< Max("Simple", "Best") <<endl;
char str1[]="Simple";
char str2[]="Best";
cout<< Max(str1, str2) <<endl;
return 0;
}
```
따라서 특수한 경우에 해당하는 함수의 형태를 직접 선언해줌으로써 해결할 수 있다.
```C++
15
T
7.5
const char* Max<const char*>(const char* a, const char* b)
Simple
char* Max<char*>(char* a, char* b)
Simple
```
# <br>클래스 템플릿
* 클래스 역시 템플릿으로 선언할 수 있다.
* 비슷한 방식으로 선언한다.
```C++
template <typename T>
class Point
{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0) : xpos(x), ypos(y)
{ }
void ShowPosition() const
{
cout<<'['<<xpos<<", "<<ypos<<']'<<endl;
}
};
int main(void)
{
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;
}
```
```C++
[3, 4]
[2.4, 3.6]
[P, F]
```
# <br>클래스 템플릿의 선언과 정의의 분리
* 선언과 정의를 분리할 수 있다.
* 일반적인 클래스 정의와 마찬가지로 디폴트 값은 헤더에만 표기한다.
* 생성자 역시 이니셜라이저는 외부에만 표시한다.
```C++
#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)
{ }
template <typename T>
void Point<T>::ShowPosition() const
{
cout<<'['<<xpos<<", "<<ypos<<']'<<endl;
}
```
# <br>템플릿의 선언과 정의의 분리
* 하지만 템플릿에서 선언과 정의의 분리는 의미가 크지 않다!
* 파일 분리가 이상해지기 때문이다.
* 템플릿 클래스의 구현은 컴파일 시기에 결정된다.
* 템플릿 클래스를 구현하기 위해서, 반드시 정의가 필요하다.
* 헤더에 선언만 있다면, 컴파일 에러를 일으킬 것이다.
* 따라서, 헤더 파일에 선언과 정의를 전부 넣는 것이 권고되는 방법이다.
# <br>예제: 템플릿 배열
다음과 같은 코드를 이용해서 경계를 확인한는 템플릿 배열을 선언할 수 있다.
```C++
template <typename T>
class BoundCheckArray
{
private:
T * arr;
int arrlen;
BoundCheckArray(const BoundCheckArray& arr) { }
BoundCheckArray& operator=(const BoundCheckArray& arr) { }
public:
BoundCheckArray(int len);
T& operator[] (int idx);
T operator[] (int idx) const;
int GetArrLen() const;
~BoundCheckArray();
};
template <typename T>
BoundCheckArray<T>::BoundCheckArray(int len) :arrlen(len)
{
arr=new T[len];
}
...
```
```C++
int main(void)
{
/*** 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];
return 0;
}
```
```C++
11
22
33
44
55
[3, 4]
[5, 6]
[7, 8]
```
# <br>템플릿 안의 템플릿 안의 템플릿 안의...
* 마찬가지 방식으로 우겨 넣으면 된다.
* Point<int>는 일종의 자료형인 셈이다.
* 따라서 BoundCheckArray<Point<int>> 역시 가능하다.
```C++
int main(void)
{
BoundCheckArray<Point<int>> oarr1(3);
oarr1[0]=Point<int>(3, 4);
oarr1[1]=Point<int>(5, 6);
oarr1[2]=Point<int>(7, 8);
for(int i=0; i<oarr1.GetArrLen(); i++)
oarr1[i].ShowPosition();
typedef Point<int>* POINT_PTR;
BoundCheckArray<POINT_PTR> oparr(3);
oparr[0]=new Point<int>(11, 12);
oparr[1]=new Point<int>(13, 14);
oparr[2]=new Point<int>(15, 16);
for(int i=0; i<oparr.GetArrLen(); i++)
oparr[i]->ShowPosition();
delete oparr[0]; delete oparr[1]; delete oparr[2];
return 0;
}
```
```C++
[3, 4]
[5, 6]
[7, 8]
[11, 12]
[13, 14]
[15, 16]
```
# <br>템플릿 클래스의 객체를 인자로 받는 함수
* 템플릿 클래스의 자료형을 대상으로도 일반 함수의 정의가 가능하다.
* 이러한 함수를 대상으로 템플릿 클래스 내에서 friend 선언도 할 수 있다.
```C++
template <typename T>
class Point
{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0): xpos(x), ypos(y)
{ }
void ShowPosition() const
{
cout<<'['<<xpos<<", "<<ypos<<']'<<endl;
}
friend Point<int> operator+(const Point<int>&, const Point<int>&);
friend ostream& operator<<(ostream& os, const Point<int>& pos);
};
Point<int> operator+(const Point<int>& pos1, const Point<int>& pos2)
{
return Point<int>(pos1.xpos+pos2.xpos, pos1.ypos+pos2.ypos);
}
ostream& operator<<(ostream& os, const Point<int>& pos)
{
os<<'['<<pos.xpos<<", "<<pos.ypos<<']'<<endl;
return os;
}
```
```C++
int main(void)
{
Point<int> pos1(2, 4);
Point<int> pos2(4, 8);
Point<int> pos3=pos1+pos2;
cout<<pos1<<pos2<<pos3;
return 0;
}
```
```C++
[2, 4]
[4, 8]
[6, 12]
```
# <br>클래스 템플릿의 특수화
```C++
template<>
class SimpleDataWrapper <char*>
{
private:
char* mdata;
public:
SimpleDataWrapper(char* data)
{
mdata=new char[strlen(data)+1];
strcpy(mdata, data);
}
void ShowDataInfo(void)
{
cout<<"String: "<<mdata<<endl;
cout<<"Length: "<<strlen(mdata)<<endl;
}
~SimpleDataWrapper()
{
delete []mdata;
}
};
template<>
class SimpleDataWrapper <Point<int>>
{
private:
Point<int> mdata;
public:
SimpleDataWrapper(int x, int y) : mdata(x, y)
{ }
void ShowDataInfo(void)
{
mdata.ShowPosition();
}
};
```
특정 자료형을 기반으로 생성된 객체에 대해 구분이 되는 다른 행동양식을 적용할 수 있다.
```C++
template <typename T>
class Point
{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0): xpos(x), ypos(y)
{ }
void ShowPosition() const
{
cout<<'['<<xpos<<", "<<ypos<<']'<<endl;
}
};
template <typename T>
class SimpleDataWrapper
{
private:
T mdata;
public:
SimpleDataWrapper(T data) : mdata(data)
{ }
void ShowDataInfo(void)
{
cout<<"Data: "<<mdata<<endl;
}
};
```
# <br>클래스 템플릿의 부분 특수화
모든 템플릿 인자에 대해서 특수화를 하지 않고, 일부에 대해서만 적용할 수 있다.
```C++
template <typename T1, typename T2>
class MySimple
{
public:
void WhoAreYou()
{
cout<<"size of T1: "<<sizeof(T1)<<endl;
cout<<"size of T2: "<<sizeof(T2)<<endl;
cout<<"<typename T1, typename T2>"<<endl;
}
};template<typename T1>
class MySimple<T1, double>
{
public:
void WhoAreYou()
{
cout<<"size of T1: "<<sizeof(T1)<<endl;
cout<<"size of double: "<<sizeof(double)<<endl;
cout<<"<T1, double>"<<endl;
}
};
```
```C++
int main(void)
{
MySimple<char, double> obj1;
obj1.WhoAreYou();
MySimple<int, long> obj2;
obj2.WhoAreYou();
MySimple<int, double> obj3;
obj3.WhoAreYou();
return 0;
}
```
```C++
size of T1: 1
size of T2: 8
<T1, double>
size of T1: 4
size of T2: 4
<typename T1, typename T2>
size of int: 4
size of double: 8
<T1, double>
```
# <br>템플릿 인자
* 템플릿 매개변수에 변수를 선언하고, 이를 인자처럼 활용할 수 있다.
* 인자가 다르면 분명 다른 자료형이므로, 이를 이용할 수 있다.
* 가령 다음 코드는 SimpleArray<int, 5>와 SimpleArray<int, 7>은 다른 자료형으로 인식되므로 이들 간의 대입을 막을 수 있다.
* 곧, 대입 연산자와 복사 생성자 등에 대한 코드 생성의 노력을 줄일 수 있는 가능성이 있다.
```C++
int main(void)
{
SimpleArray<int, 5> i5arr1;
for(int i=0; i<5; i++)
i5arr1[i]=i*10;
SimpleArray<int, 5> i5arr2;
i5arr2=i5arr1;
for(int i=0; i<5; i++)
cout<<i5arr2[i]<<", ";
cout<<endl;
SimpleArray<int, 7> i7arr1;
for(int i=0; i<7; i++)
i7arr1[i]=i*10;
SimpleArray<int, 7> i7arr2;
i7arr2=i7arr1;
for(int i=0; i<7; i++)
cout<<i7arr2[i]<<", ";
cout<<endl;
return 0;
}
```
```C++
template <typename T, int len>
class SimpleArray
{
private:
T arr[len];
public:
T& operator[] (int idx)
{
return arr[idx];
}
T& operator=(const T&ref)
{
for(int i=0; i<len; i++)
arr[i]=ref.arr[i];
}
};
```
```C++
0, 10, 20, 30, 40,
0, 10, 20, 30, 40, 50, 60,
```
# <br>템플릿 인자의 디폴트 값
* 함수의 매개변수와 같이 템플릿 매개변수에도 디폴트 값의 지정이 가능하다.
* 디폴트 값이 지정되어도, 템플릿 클래스의 객체 생성을 의미하는 <> 기호는 반드시 필요하다.
```C++
template <typename T=int, int len=7>
class SimpleArray
{
private:
T arr[len];
public:
T& operator[] (int idx)
{
return arr[idx];
}
T& operator=(const T&ref)
{
for(int i=0; i<len; i++)
arr[i]=ref.arr[i];
}
};
```
```C++
int main(void)
{
SimpleArray<> arr;
for(int i=0; i<7; i++)
arr[i]=i+1;
for(int i=0; i<7; i++)
cout<<arr[i]<<" ";
cout<<endl;
return 0;
}
```
# <br>템플릿과 static
* ShowStaticValue()는 static 변수를 사용한다.
* 이들 역시 컴파일 시기에 여러 종류의 함수가 생성되는 것이므로, static 지역 변수들 역시 따로따로 존재한다.
```C++
template <typename T>
void ShowStaticValue(void)
{
static T num=0;
num+=1;
cout<<num<<" ";
}
int main(void)
{
ShowStaticValue<int>();
ShowStaticValue<int>();
ShowStaticValue<int>();
cout<<endl;
ShowStaticValue<long>();
ShowStaticValue<long>();
ShowStaticValue<long>();
cout<<endl;
ShowStaticValue<double>();
ShowStaticValue<double>();
ShowStaticValue<double>();
return 0;
}
```
```C++
1 2 3
1 2 3
1 2 3
```
# <br>클래스 템플릿과 static 멤버 변수
* 클래스 템플릿 내에 선언된 static 멤버 변수 역시 함수와 동일하다.
* 컴파일 시기에 생성된 각각의 템플릿 클래스들이 각각의 static 멤버를 관리한다.
```C++
template <typename T>
class SimpleStaticMem
{
private:
static T mem;
public:
void AddMem(int num) { mem+=num; }
void ShowMem() { cout<<mem<<endl; }
} ;
template <typename T>
T SimpleStaticMem<T>::mem=0;
template<>
long SimpleStaticMem<long>::mem=5;
```
```C++
int main(void)
{
SimpleStaticMem<int> obj1;
SimpleStaticMem<int> obj2;
obj1.AddMem(2);
obj2.AddMem(3);
obj1.ShowMem();
SimpleStaticMem<long> obj3;
SimpleStaticMem<long> obj4;
obj3.AddMem(100);
obj4.ShowMem();
return 0 ;
}
```
# <br>template <typename T>와 template<>
* 언제 각각을 써야 하는가?
* 템플릿 관련 정의에는 template<typename T> 또는 template<>을 써 템플릿의 일부를 정의하고 있다는 사실을 컴파일러에게 알린다.
* 템플릿을 정의하고 있으나, 해당하는 템플릿 매개 변수가 필요 없을 때 template<>을 사용한다.
* 가령: int로 특수화하는 경우에는 템플릿 매개 변수가 필요 없다.
```C++
template <typename T>
class SoSimple
{
public:
T SimpleFunc(T num) {}
};
template <>
class SoSimple<int>
{
public:
int SimpleFunc(int num) {}
};
```
# <br>예외상황
* 예외(exception)
* 런타임에 발생하는 문제 상황
* 나이를 입력하라고 했는데, 0보다 작은 값이 입력되었다.
* 0으로 나누었다.
* 주민등록번호 13자리만 입력하라고 했더니, 중간에 –를 포함하여 14자리를 입력하였다.
* 다음 코드는 예외를 발생시킬 여지가 있다: 0으로 나눌 수 있다.
```C++
#include <iostream>
using namespace std;
int main(void)
{
int num1, num2;
cout<<"두 개의 숫자 입력: ";
cin>>num1>>num2;
cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
return 0;
}
```
# <br>if문을 이용한 예외처리
* if문을 이용해서 문제 상황을 처리할 수 있다.
* 예외처리를 위한 코드와 프로그램의 흐름을 구성하는 코드를 구분하기 어렵다.
* 수빈의 첨언:
* 저자는 프로그램의 논리적인 기능과 예외처리는 다르다고 이야기한다.
* 이는 논쟁의 여지가 있는데, 예외처리 역시 프로그램의 로직과 다르지 않다고 볼 수 있기 때문이다.
* 나는 구조적인 설명을 선호한다:
* A라는 목표를 달성 중인 논리 흐름에서, 특정한 조건에서는 언제든지 그 중간에 A를 달성하지 않고 넘어가고 싶을 때 예외처리를 활용할 수 있다고 말하고 싶다.
* 즉, try-catch는 if와 달리 구조적으로 scope을 벗어나 jump할 수 있다.
* 마치 프로그램 흐름 관점에서의 continue
```C++
#include <iostream>
using namespace std;
int main(void)
{
int num1, num2;
cout<<"두 개의 숫자 입력: ";
cin>>num1>>num2;
if(num2==0)
{
cout<<"젯수는 0이 될 수 없습니다."<<endl;
cout<<"프로그램을 다시 실행하세요."<<endl;
}
else
{
cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
}
return 0;
}
```
# <br>C++의 예외처리 메커니즘
try: 예외를 발견한다.
catch: 예외를 잡는다.
throw: 예외를 던진다.
try 블록 내에서 예외가 발생하지 않으면, catch 블록 이후를 실행한다.
try 블록 내에서 예외가 발생하면, 해당하는 catch 블록으로 jump한다.
```C++
#include <iostream>
using namespace std;
int main(void)
{
int num1, num2;
cout<<"두 개의 숫자 입력: ";
cin>>num1>>num2;
try
{
if(num2==0)
throw num2;
cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
}
catch(int expn)
{
cout<<"젯수는 "<<expn<<"이 될 수 없습니다."<<endl;
cout<<"프로그램을 다시 실행하세요."<<endl;
}
cout<<"end of main"<<endl;
return 0;
}
```
```C++
두 개의 숫자 입력: 9 2
나눗셈의 몫: 4
나눗셈의 나머지: 1
end of main
```
```C++
두 개의 숫자 입력: 7 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
end of main
```
# <br>예외의 전달
```C++
#include <iostream>
using namespace std;
void Divide(int num1, int num2)
{
if(num2==0)
throw num2;
cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
}
int main(void)
{
int num1, num2;
cout<<"두 개의 숫자 입력: ";
cin>>num1>>num2;
try
{
Divide(num1, num2);
cout<<"나눗셈을 마쳤습니다."<<endl;
}
catch(int expn)
{
cout<<"제수는 "<<expn<<"이 될 수 없습니다."<<endl;
cout<<"프로그램을 다시 실행하세요."<<endl;
}
return 0;
}
```
* 예외처리에 대한 책임은 콜스택을 빠져나오면서 만나는 최초의 try문에 있다.
* 만약 없다면, 그대로 프로그램은 종료된다.
* 운영체제가 예외를 처리한 셈이다.
* 이러한 과정을 Stack Unwinding이라고 한다.
* catch의 자료형과 throw된 데이터의 자료형이 일치하지 않는 경우:
* 해당 catch문은 무시되며, 계속해서 위로 Stack Unwinding된다.
```C++
두 개의 숫자 입력: 9 2
나눗셈의 몫: 4
나눗셈의 나머지: 1
나눗셈을 마쳤습니다.
```
```C++
두 개의 숫자 입력: 7 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
```
# <br>하나의 try 다수의 catch
```C++
int main(void)
{
char str1[100];
char str2[200];
while(1)
{
cout<<"두 개의 숫자 입력: ";
cin>>str1>>str2;
try
{
cout<<str1<<" + "<<str2<<" = "<<StoI(str1)+StoI(str2)<<endl;
break;
}
catch(char ch)
{
cout<<"문자 "<< ch <<"가 입력되었습니다."<<endl;
cout<<"재입력 진행합니다."<<endl<<endl;
}
catch(int expn)
{
if(expn==0)
cout<<"0으로 시작하는 숫자는 입력불가."<<endl;
else
cout<<"비정상적 입력이 이루어졌습니다."<<endl;
cout<<"재입력 진행합니다."<<endl<<endl;
}
}
cout<<"프로그램을 종료합니다."<<endl;
return 0;
}
```
```C++
int StoI(char * str)
{
int len=strlen(str);
int num=0;
if(len!=0 && str[0]=='0')
throw 0;
for(int i=0; i<len; i++)
{
if(str[i]<'0' || str[i]>'9')
throw str[i];
num += (int)(pow((double)10, (len-1)-i) * (str[i]+(7-'7')));
}
return num;
}
```
```C++
두 개의 숫자 입력: 12A 519
문자 A가 입력되었습니다.
재입력 진행합니다.
두 개의 숫자 입력: 082 910
0으로 시작하는 숫자는 입력불가.
재입력 진행합니다.
두 개의 숫자 입력: 123 456
123 + 456 = 579
프로그램을 종료합니다.
```
# <br>전달되는 예외의 명시
* 함수 내에서 발생할 수 있는 예외 역시 함수의 특징으로 간주된다.
* 오버로딩되는 것은 아니다.
* 헤더에는 이를 명시해주는 것이 좋다.
* 그래야 해당 함수의 클라이언트가 예외 역시 잘 처리할 수 있을 것이다.
```C++
int ThrowFunc(int num) throw (int, char)
{ }
try
{
ThrowFunc(20);
}
catch(int expn) { }
catch(char expn) { }
```
# <br>예외 클래스와 예외 객체
```C++
class AccountException
{
public:
virtual void ShowExceptionReason() =0;
};
class DepositException : public AccountException
{
private:
int reqDep; // 요청 입금액
public:
DepositException(int money) : reqDep(money)
{ }
void ShowExceptionReason()
{
cout<<"[예외 메시지: "<<reqDep<<"는 입금불가]"<<endl;
}
};
class WithdrawException : public AccountException
{
private:
int balance; // 잔고
public:
WithdrawException(int money) : balance(money)
{ }
void ShowExceptionReason()
{
cout<<"[예외 메시지: 잔액 "<<balance<<", 잔액부족]"<<endl;
}
};
```
예외 발생을 알리는 데 사용되는 객체를 예외 객체라 하며, 예외 객체를 정의하는 예외 클래스가 있다.
```C++
int main(void)
{
Account myAcc("56789-827120", 5000);
try
{
myAcc.Deposit(2000);
myAcc.Deposit(-300);
}
catch(AccountException &expn)
{
expn.ShowExceptionReason();
}
myAcc.ShowMyMoney();
return 0;
}
```
```C++
[예외 메시지: -300는 입금불가]
잔고: 7000
```
# <br>catch의 주의사항
* catch는 위에서부터 차례대로 수행되며, 자료형에 맞지 않으면 다음으로 넘긴다.
* 상속으로 인한 문제에 주의해야 한다!
* 반대로 catch를 적용하면 원하는 결과를 얻을 수 있다.
```C++
int main(void)
{
try
{
throw CCC();
}
catch(AAA& expn)
{
cout<<"catch(AAA& expn)"<<endl;
expn.ShowYou();
}
catch(BBB& expn)
{
cout<<"catch(BBB& expn)"<<endl;
expn.ShowYou();
}
catch(CCC& expn)
{
cout<<"catch(CCC& expn)"<<endl;
expn.ShowYou();
}
return 0;
}
```
```C++
class AAA
{
public:
void ShowYou() { cout<<"AAA exception!"<<endl; }
};
class BBB : public AAA
{
public:
void ShowYou() { cout<<"BBB exception!"<<endl; }
};
class CCC : public BBB
{
public:
void ShowYou() { cout<<"CCC exception!"<<endl; }
};
```
```C++
catch(AAA& expn)
AAA exception!
```
# <br>bad_alloc
new 연산에 의한 메모리 공간의 할당이 실패하면 던져지는 예외이다.
헤더파일 <new>에 선언된 예외 클래스로써 메모리 공간의 할당이 실패했음을 알리는 의도로 정의되었다.
what() 함수는 예외의 원인 정보를 문자열의 형태로 반환하며, 반환되는 문자열의 내용은 컴파일러에 따라서 달라진다.
```C++
#include <iostream>
#include <new>
using namespace std;
int main(void)
{
int num=0;
try
{
while(1)
{
num++;
cout<<num<<"번째 할당"<<endl;
new int[10000][10000];
}
}
catch(bad_alloc &bad)
{
cout<<bad.what()<<endl;
cout<<"더 이상 할당 불가!"<<endl;
}
return 0;
}
```
```C++
1번째 할당 시도
2번째 할당 시도
3번째 할당 시도
4번째 할당 시도
5번째 할당 시도
bad allocation
더 이상 할당 불가!
```
# <br>모든 예외를 처리하는 catch 블록
* 다음과 같이 catch 블록을 선언하면, try 블록 내에서 전달되는 모든 예외가 자료형에 상관없이 걸려든다.
* 마지막 catch 블록에 덧붙여지는 경우가 많다.
* 발생한 예외에 관련해서 어떠한 정보도 전달받을 수 없다.
* 전달된 예외의 종류도 구분이 불가능하다.
```C++
try
{
}
catch(...)
{
}
```
# <br>예외 던지기
```C++
#include <iostream>
using namespace std;
void Divide(int num1, int num2)
{
try
{
if(num2==0)
throw 0;
cout<<"몫: "<<num1/num2<<endl;
cout<<"나머지: "<<num1%num2<<endl;
}
catch(int expn)
{
cout<<"first catch"<<endl;
throw;
}
}
int main(void)
{
try
{
Divide(9, 2);
Divide(4, 0);
}
catch(int expn)
{
cout<<"second catch"<<endl;
}
return 0;
}
```
* catch 블록에 전달된 예외는 다시 던져질 수 있다.
* 하나의 예외가 둘 이상의 catch 블록에서 처리되게 된다.
* 예외처리는 가급적 간결한 구조를 띠는 게 좋다.
* 정말로 필요한 상황이 아니라면 다음과 같은 구조를 가질 필요는 없다.
```C++
몫: 4
나머지 :1
first catch
second catch
```
# <br>형 변환 연산자
```C++
class Car
{
private:
int fuelGauge;
public:
void ShowCarState()
{
cout<<"잔여 연료량: "<<fuelGauge<<endl;
}
};class Truck : public Car
{
private:
int freightWeight;
public:
void ShowTruckState()
{
ShowCarState();
cout<<"화물의 무게: "<<freightWeight<<endl;
}
};int main(void)
{
Car * pcar1=new Truck(80, 200);
Truck * ptruck1=(Truck *)pcar1;
ptruck1->ShowTruckState();
cout<<endl;
Car * pcar2=new Car(120);
Truck * ptruck2=(Truck *)pcar2;
ptruck2->ShowTruckState();
return 0;
}
```
* C언어의 형 변환 연산자는 강력해서 실수를 일으키기 쉽다.
* 다음의 코드 역시 가능하다:
* 기초 클래스 포인터를 유도 클래스의 포인터로 캐스팅할 수 있다.
* 이는 때로 필요한데, 기초 클래스 포인터가 실제로 유도 클래스 객체를 가리키고 있을 수 있기 때문이다.
* 이는 예상치 못한 동작을 일으키게 될 가능성이 존재한다.
```C++
잔여 연료량: 80
화물의 무게: 200
잔여 연료량: 120
화물의 무게: 3801464
```
* dynamic_cast
* 상속관계에서의 안전한 형 변환
* 상속 관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우 사용한다.
* 하지만 분명 A의 경우는 때에 따라서 필요해 보인다.
```C++
int main(void)
{
Car * pcar1=new Truck(80, 200);
Truck * ptruck1=dynamic_cast<Truck*>(pcar1); // A: 컴파일 에러
Car * pcar2=new Car(120);
Truck * ptruck2=dynamic_cast<Truck*>(pcar2); // B: 컴파일 에러
Truck * ptruck3=new Truck(70, 150);
Car * pcar3=dynamic_cast<Car*>(ptruck3); // C: 컴파일 OK!
return 0;
}
```
* static_cast
* 유도 클래스의 포인터 및 참조형을 기초 클래스의 포인터 및 참조형으로뿐만 아니라, 기초 클래스의 포인터 및 참조형도 유도 클래스의 포인터 및 참조형으로 형 변환할 수 있다.
* static_cast의 활용은 편리하나, 분명 오작동을 일으킬 수 있다.
* 따라서 dynamic_cast를 활용할 수 있다면 dynamic_cast를 활용해 안정성을 높여야 한다.
```C++
int main(void)
{
Car * pcar1=new Truck(80, 200);
Truck * ptruck1=static_cast<Truck*>(pcar1); // 컴파일 OK!
ptruck1->ShowTruckState();
cout<<endl;
Car * pcar2=new Car(120);
Truck * ptruck2=static_cast<Truck*>(pcar2); // 컴파일 OK! 그러나!
ptruck2->ShowTruckState();
return 0;
}
```
```C++
잔여 연료량: 80
화물의 무게: 200
잔여 연료량: 120
화물의 무게: 3801464
```
* static_cast
* static_cast는 기본 자료형 데이터 간의 형 변환에도 사용이 된다.
* 두 문장은 동일하며, 후자가 권고되는 방식이다.
* 왜냐하면 다음과 같은 캐스팅이 이루어질 수 있으며, static_cast는 이를 방지한다.
```C++
double result = (double)20 / 3;
double result = static_cast<double>(20)/3;
```
```C++
int main(void)
{
const int num = 20;
int *ptr = (int*)#
*ptr = 30;
float *adr = (float*)ptr;
}
```
* const_cast
* 포인터와 참조자의 const 성향을 제거하는 형 변환을 할 수 있다.
* const_cast는 개발자의 의도를 해칠 수 있으므로, 분명히 필요할 때에만 사용해야 한다.
```C++
#include <iostream>
using namespace std;
void ShowString(char* str)
{
cout<<str<<endl;
}
void ShowAddResult(int& n1, int& n2)
{
cout<<n1+n2<<endl;
}
int main(void)
{
const char * name="Lee Sung Ju";
ShowString(const_cast<char*>(name));
const int& num1=100;
const int& num2=200;
ShowAddResult(const_cast<int&>(num1), const_cast<int&>(num2));
return 0;
}
```
* reinterpret_cast
* 포인터를 대상으로 하는, 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다
* 주소 역시 정수로 변환하는 것도 가능하다.
```C++
#include <iostream>
using namespace std;
int main(void)
{
int num=0x010203;
char * ptr=reinterpret_cast<char*>(&num);
for(int i=0; i<sizeof(num); i++)
cout<<static_cast<int>(*(ptr+i))<<endl;
return 0;
}
```
```C++
int num = 72;
int *ptr = #
int adr = reinterpret_cast<int>(ptr);
int *rptr = reinterpret_cast<int*>(adr);
```
# <br>dynamic_cast와 Polymorphic 클래스
* dynamic_class는 유도 클래스로의 형 변환을 허용하는 경우가 있다:
* 기초 클래스가 Polymorphic 클래스인 경우
* 즉, 하나 이상의 가상함수를 지니는 클래스인 경우이다.
```C++
#include <iostream>
using namespace std;
class SoSimple
{
public:
virtual void ShowSimpleInfo()
{
cout<<"SoSimple Base Class"<<endl;
}
};
class SoComplex : public SoSimple
{
public:
void ShowSimpleInfo()
{
cout<<"SoComplex Derived Class"<<endl;
}
};
int main(void)
{
SoSimple * simPtr=new SoComplex;
SoComplex * comPtr=dynamic_cast<SoComplex*>(simPtr);
comPtr->ShowSimpleInfo();
return 0;
}
```
```C++
SoComplex Derived Class
```
* static_cast와의 차이
* Polymorphic 클래스에 대한 dynamic_cast가 성공한 이유는, 실제로 가능했기 때문이다.
* 만약 불가능하다면, NULL을 반환한다.
* dynamic_cast는 런타임에 동적으로 캐스팅 가능 여부를 확인한다.
* 성능은 약간 느려지지만, 안정적인 형 변환이 가능하다.
```C++
int main(void)
{
SoSimple * simPtr=new SoSimple;
SoComplex * comPtr=dynamic_cast<SoComplex*>(simPtr);
if(comPtr==NULL)
cout<<"형 변환 실패"<<endl;
else
comPtr->ShowSimpleInfo();
return 0;
}
```
# <br>bad_cast
* dynamic_cast는 형 변환 실패의 경우 NULL을 반환한다.
* 하지만 그 대상이 참조형이라면 NULL을 참조할 수 없으므로, 예외가 발생한다.
```C++
int main(void)
{
SoSimple simObj;
SoSimple& ref=simObj;
try
{
SoComplex& comRef=dynamic_cast<SoComplex&>(ref);
comRef.ShowSimpleInfo();
}
catch(bad_cast expt)
{
cout<<expt.what()<<endl;
}
return 0;
}
```
```C++
Bad dynamic_cast!
```
댓글
댓글 쓰기