[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! ```
댓글
댓글 쓰기