{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[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!
```

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기