예외처리 (Exception Handling) 박종혁교수 UCS Lab Tel: 970-6702 Email: jhpark1@seoultech.ac.kr SeoulTech 2012-2 nd 프로그래밍입문 (1)
예외상황과예외처리의이해
3 예외상황을처리하지않았을때의결과 예외상황은프로그램실행중에발생하는문제의상황을의미한다. 예외상황의예나이를입력하라고했는데, 0보다작은값이입력됨. 나눗셈을위해서두개의숫자를입력받았는데, 제수로 0이입력됨. 주민등록번호 13자리만입력하라고했는데중간에 가삽입됨. 이렇듯예외는문법적오류가아닌, 프로그램논리에맞지않는오류를뜻한다. 실행결과 1 실행결과 2
4 if 문을이용한예외의처리 if 문을이용해서예외를발견하고처리하면, 예외처리부분과일반적인프로그램의흐름을쉽게구분할수없다. if 문은일반적은프로그램의논리를구현하는데주로사용된다. 그래서 C++ 은별도의예외처리메커니즘을제공하고있다. if 문을통해서예외를발견 num2 에 0 이입력됨으로인해서예외가발생 if 문안에서예외를처리! 실행결과
C++ 의예외처리메커니즘
6 C++ 의예외처리메커니즘이해 : try, catch, throw try 블록은예외발생에대한검사범위를지정하는데사용된다. catch 블록은 try 블록에서발생한예외를처리하는영역으로그형태가마차반환형없는함수와같다. throw는예외의발생을알리는역할을한다. try~catch는하나의문장이므로그사이에다른문장이삽입될수없다.
7 예외처리메커니즘의적용 실행결과 1 실행결과 2 예외의발생으로인해서 try 블록내에서 throw 절이실행이 되면, try 블록의나머지부분은실행이되지않는다! 실행의흐름을이해하는것이중요! 실행의흐름은 try 블록안으로들어간다. 그안에서예외가발생하면이후에등장하는 catch 블록을실행하게된다. 예외가발생하지않으면 try 블록을빠져나와 try~ catch 블록이후를실행하게된다.
8 try 블록을묶는기준 잘묶인예! 잘못묶인예! 예외와연관이있는부분을모두하나의 try 블록으로묶어야한다!
Stack Unwinding( 스택풀기 )
10 예외의전달 예외가 Divide 함수내에서발생한다! 그런데발생한지점을 (throws 절을 ) 감싸는 try 블록이존재하지않는다! 예외가처리되지않으면, 예외가발생한함수를호출한영역으로예외데 이터가 ( 더불어예외처리에대한책임까지 ) 전달된다
11 예외의발생위치와처리위치가달라야하는경우 함수의비정상종료에따른처리를 main 함수에서해야 하므로예외의처리는 main 함수에서진행되어야한다. 예외의데이터가전달되면, 함수는더이상실행되지않고종료된다. 실행결과
12 스택풀기 (Stack Unwinding) 예외가처리되지않아서함수를호출한영역으로예외데이터가전달되는현상을가리켜 ' 스택풀기 ' 라고한다. main 함수에서조차예외를처리하지않으면, terminate 함수 ( 프로그램을종료시키는함수 ) 가 호출되면서프로그램이종료됨.
StackUnwinding.cpp #include <iostream> using namespace std; 13 void SimpleFuncOne(void); void SimpleFuncTwo(void); void SimpleFuncThree(void); int main(void) try SimpleFuncOne(); catch(int expn) cout<<" 예외코드 : "<< expn <<endl; return 0; void SimpleFuncOne(void) cout<<"simplefuncone(void)"<<endl; SimpleFuncTwo(); void SimpleFuncTwo(void) cout<<"simplefunctwo(void)"<<endl; SimpleFuncThree(); void SimpleFuncThree(void) cout<<"simplefuncthree(void)"<<endl; throw -1;
14 자료형이일치하지않아도예외데이터는전달 형변환발생하지않아서예외데이터는 SimpleFunc 함수를호출한영역으로전달된다.
15 하나의 try 블록과다수의 catch 블록 하나의 try 영역내에서종류가다른둘이상의예외가발생할수있기때문에, 하나의 try 블록에다수의 catch 블록을 추가할수있다.
StackUnwinding.cpp #include <iostream> #include <cstring> #include <cmath> using namespace std; 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; int main(void) char str1[100]; char str2[200]; 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; 16 while(1) cout<<" 두개의숫자입력 : "; cin>>str1>>str2;
17 전달되는예외의명시 함수내에서예외상황의발생으로인해서 int 형예외데이터와 char 형예외데이터가전달될수있음을명시한선언 따라서이함수를호출하는영역의코드는다음과같이구성해 야한다. int, char 이외의예외데이터가전달되면, terminate 함수의호출로인해서프로그램이종료된다. 프로그램의종료는대비하지못한예외상황의처리를알리는의미로받아들여진다. 어떠한예외가발생해도프로그램은종료가된다! 이함수는어떠한예외상황도발생하지않는다는의미의함수선언!
예외상황을표현하는예외클래스의설계
19 예외클래스와예외객체 예외객체 : 예외발생을알리는데사용되는객체 예외클래스 : 예외객체의생성을위해정의된클래스 객체를이용해서예외상황을알리면예외가발생한원인에대한정보를보다자세히담을수있다. class DepositException 입금관련예외상황의표현을위해서정의된클래스 private: int reqdep; // 요청입금액 public: DepositException(int money) : reqdep(money) void ShowExceptionReason() cout<<"[ 예외메시지 : "<<reqdep<<" 는입금불가 ]"<<endl; 예외클래스 ; class WithdrawException 출금관련예외상황의표현을위해서정의된클래스 private: int balance; // 잔고 public: WithdrawException(int money) : balance(money) void ShowExceptionReason() cout<<"[ 예외메시지 : 잔액 "<<balance<<", 잔액부족 ]"<<endl; ; 예외클래스
20 예외클래스기반의예외처리 class Account private: char accnum[50]; // 계좌번호 int balance; // 잔고 public: Account(char * acc, int money) : balance(money) strcpy(accnum, acc); void Deposit(int money) throw (DepositException) if(money<0) DepositException expn(money); throw expn; 객체형태의예외데이터 balance+=money; void Withdraw(int money) throw (WithdrawException) if(money>balance) throw WithdrawException(balance); balance-=money; 임시객체의형태로전달가능! void ShowMyMoney() cout<<" 잔고 : "<<balance<<endl<<endl; ; int main(void) Account myacc("56789-827120", 5000); try myacc.deposit(2000); myacc.deposit(-300); catch(depositexception &expn) expn.showexceptionreason(); 예외객체의멤버함수호출 myacc.showmymoney(); try myacc.withdraw(3500); myacc.withdraw(4500); catch(withdrawexception &expn) expn.showexceptionreason(); myacc.showmymoney(); 예외객체의멤버함수호출 return 0;
ATMSim.cpp #include <iostream> #include <cstring> using namespace std; class DepositException private: int reqdep; // 요청입금액 public: DepositException(int money) : reqdep(money) void ShowExceptionReason() cout<<"[ 예외메시지 : "<<reqdep<<" 는입금불가 ]"<<endl; ; class WithdrawException private: int balance; // 잔고 public: WithdrawException(int money) : balance(money) void ShowExceptionReason() cout<<"[ 예외메시지 : 잔액 "<<balance<<", 잔액부족 ]"<<endl; ; 21 class Account private: char accnum[50]; // 계좌번호 int balance; // 잔고 public: Account(char * acc, int money) : balance(money) strcpy(accnum, acc); void Deposit(int money) throw (DepositException) if(money<0) DepositException expn(money); throw expn; balance+=money; void Withdraw(int money) throw (WithdrawException) if(money>balance) throw WithdrawException(balance); balance-=money; void ShowMyMoney() cout<<" 잔고 : "<<balance<<endl<<endl; ;
ATMSim.cpp int main(void) Account myacc("56789-827120", 5000); 22 try myacc.deposit(2000); myacc.deposit(-300); catch(depositexception &expn) expn.showexceptionreason(); myacc.showmymoney(); try myacc.withdraw(3500); myacc.withdraw(4500); catch(withdrawexception &expn) expn.showexceptionreason(); myacc.showmymoney(); return 0;
23 상속관계에있는예외클래스 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; ; try myacc.deposit(2000); myacc.deposit(-300); catch(accountexception &expn) expn.showexceptionreason(); try myacc.withdraw(3500); myacc.withdraw(4500); catch(accountexception &expn) expn.showexceptionreason(); Depos~ 예외클래스와 With~ 예외클래스는 AccountException 클래스를상속하므로 AccountException 을대상으로정의된 catch 블록에의해처리될수있다.
24 예외의전달방식에따른주의사항 맨위에있는 catch 블록부터시작해서아래 로적절한 catch 블록을찾아내려온다 예외객체의전달과정에서의문제점은?
CatchFlow.cpp #include <iostream> using namespace std; 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; ; void ExceptionGenerator(int expn) if(expn==1) throw AAA(); else if(expn==2) throw BBB(); else throw CCC(); int main(void) try ExceptionGenerator(3); ExceptionGenerator(2); ExceptionGenerator(1); 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; 25
예외처리와관련된또다른특성들
27 new 연산자에의해서전달되는예외 오늘날 C++ 표준에서는 new 연산자가메모리할당에실패 ==> 헤더파일 new 에선언되어있는 bad_alloc 예외처리가전달된다고명시됨 실행결과 bad_alloc 과같이프로그래머가정의하지않아도발생하는예외도있다. 그리고 Chapter 16 에서는이러한유형의예외중하나로, 형변환시발생하는 bad_cast 예외를소개한다.
28 모든예외를처리하는 catch 블록 마지막 catch 블록에덧붙여지는경우가많은데, 대신 catch 의매개변수선언에서보이듯이, 발생한예외와관련해서그어떠한정보도전달받을수없으며, 전달된예외의종류도구분이불가능하다.
29 예외던지기 catch 블록에전달된예외는다시던져질수있다. 그리고이로인해서하나의예외가둘이상의 catch 블록에의해서처리되게할수있다. 실행결과
Q & A