013. 예외처리(Exception Handling)
I. 기존의 예외처리 방식
- 예외를 처리하지 않는 프로그램의 오류
- #include <iostream>
- using namespace std;
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b; - cout<<"a/b의 몫 : "<<a/b<<endl;
cout<<"a/b의 나머지 : "<<a%b<<endl;
return 0;
}
-
위의 소스는 정말 쉬운 소스이다.
- 하지만 치명적인 오류가 있다. 다들 알다시피 b가 0일때의 문제이기 때문이다.
- 전통적인 스타일의 예외처리
- #include <iostream>
- using namespace std;
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b; - cout<<"a/b의 몫 : "<<a/b<<endl;
cout<<"a/b의 나머지 : "<<a%b<<endl;
return 0;
}
-
위와 같은 식으로 하면 당연히 오류가 발생하는 것을 막을수는 있다.
-
하지만 예외처리를 위한 코드 부분과 일반적인 프로그램의 흐름을 위한 코드 부분을 명확히 구분 짓지 못한다.
- 짧은 소스일때는 상관 없지만 길경우 구별하기 힘들다.
-
II. 기본적인 예외 처리 메커니즘(try, catch, throw)
-
try
- 예외 발생에 대한 검사 범위를 설정 할 때 사용한다.
- try
- {
- //예외 발생 예상 코드
- }
-
catch
- 예외를 처리하는 코드 구간을 선언할때 사용한다. try 구간 내에서 발생한 예외 상황을 처리하는 코드가 존재하는 영역이다.
- catch(처리되어야 할 예외의 종류)
- {
- //예외를 처리하는 코드가 존재할 위치
- }
-
try와 catch
- try바로 뒤에 catch가 등장한다.
-
throw
- 예외 상황이 발생 하였음을 알릴때 사용
- try
- {
- if(예외 상황 발생)
- throw ex;
- }
- catch(exception ex)
- {
- 예외 상황 처리
- }
- 예외 처리 메커니즘의 적용
- #include <iostream>
- using namespace std;
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b;
try
{
if(b==0)
throw b;
cout<<"a/b의 몫 : "<<a/b<<endl;
cout<<"a/b의 나머지 : "<<a%b<<endl;
}
catch (int exception)
{
cout<<exception<<" 입력."<<endl;
cout<<"입력 오류! 다시 실행하세요"<<endl;
}
return 0;
}
- 위와 같이 하면 된다.
- 예외 처리 적용 시 프로그램의 흐름
- #include <iostream>
- using namespace std;
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b;
try
{
cout<<"try block start"<<endl; - if(b==0)
throw b;
cout<<"a/b의 몫 : "<<a/b<<endl;
cout<<"a/b의 나머지 : "<<a%b<<endl; - cout<<"try block end"<<endl;
}
catch (int exception)
{
cout<<"catch block start"<<endl; - cout<<exception<<" 입력."<<endl;
cout<<"입력 오류! 다시 실행하세요"<<endl;
}
cout<<"Thack You!"<<endl;
return 0;
}
- 위 소스를 실행하면 어떻게 흘러가는지 알수 있다.
III. Stack Unwinding(스택 풀기)
- 전달되는 예외
- #include <iostream>
- using namespace std;
- int divide(int a, int b);
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b;
try
{
cout<<"a/b의 몫 : "<<divide(a,b)<<endl;
}
catch (int exception)
{
cout<<exception<<" 입력."<<endl;
cout<<"입력 오류! 다시 실행하세요"<<endl;
}
return 0;
} - int divide(int a, int b)
{
if(b==0)
throw b;
return a/b;
}
-
처리되지 않은 예외는 전달된다는 것이다.
- 이러한 현상을 가리켜 스택 unwinding이라고 한다.
- #include <iostream>
- using namespace std;
- void fct1();
void fct2();
void fct3(); - int main()
{
try
{
fct1();
}
catch(int ex)
{
cout<<"예외 : "<<ex<<endl;
}
return 0;
} - void fct1()
{
fct2();
} - void fct2()
{
fct3();
} - void fct3()
{
throw 100;
}
- 예외가 전달되는 과정이 함수의 스택이 풀리는 순서와 일치하기 때문에 스택 unwinding 이라고 한다.
- 처리되지 않은 예외
- #include <iostream>
- using namespace std;
- int divide(int a, int b);
- int main()
{
int a,b;
cout<<"입력 : ";
cin>>a>>b;
cout<<"a/b의 몫 : "<<divide(a,b)<<endl;
return 0;
} - int divide(int a, int b)
{
if(b==0)
throw b;
return a/b;
}
-
위와 같은 소스를 실행 시켜서 오류를 나게 해보자
- b가 0이면 예외가 발생을 한다. 하지만 여기서 보면 예외를 처리해 주는 부분이 존재하지 않는다.
- 이러한 경우 stdlib.h 안에 abort 함수가 호출되면서 프로그램을 종료 시킨다.
- #include <iostream>
#include <stdlib.h> - using namespace std;
int main()
{
abort();
cout<<"end"<<endl;
return 0;
}
- 위와 같은 소스를 실행 시키면 오류메시지를 띄우면서 cout<<"end"<<endl; 위에서 종료가 된다.
- #include <iostream>
- using namespace std;
- int divide(int a,int b);
- int main()
{
int a, b; - cout<<"두개 입력 : ";
cin>>a>>b; - try
{
cout<<"a/b의 몫 : "<<divide(a,b)<<endl;
}
catch (char exception)
{
cout<<exception<<" 입력."<<endl;
cout<<"입력 오류!"<<endl;
}
return 0;
} - int divide(int a,int b)
{
if(b==0)
throw b;
return a/b;
}
-
위의 소스를 보면 예외를 처리해 주는 부분이 있다.
- 다만 catch구문에서 char형 예외를 처리하겠다는 것만 있기 때문에 abort 함수가 호출된다.
-
전달되는 예외 명시하기
- 함수를 정의하는데 있어서 전달될수 있는 예외의 종류를 명시해 줄 수 있다.
- int fct(double b) throw(int)
- {
- ...
- }
-
상황에 따라서 int형 예외가 전달될 수 있음을 선언하고 있는 것이다.
- int형이 아닌 다른형의 예외가 발생하면 윗부분에서 봤다시피 abort함수가 호출된다.
- int fct(double b) throw(int, double, char*)
- {
- ...
- }
- 위에 처럼 둘이상의 예외종류를 선언해 줄 수 있다.
- int fct(double b) throw()
- {
- ...
- }
-
위와 같은 경우는 어떠한 예외도 전달하지 않는다는 것이다.
- 만약에 예외가 전달된다면 abort함수가 호출된다.
- 하나의 try 블록과 여러개의 catch 블록
- #include <iostream>
- using namespace std;
- int main()
{
int num;
cout<<" input : ";
cin>> num; - try
{
if(num>0)
throw 10;
else
throw 'm';
}
catch (int exp)
{
cout<<"int형 예외 발생"<<endl;
}
catch (char exp)
{
cout<<"char형 예외 발생 "<<endl;
}
return 0;
}
-
위의 소스 처럼 0보다 크면 int형 예외, 그 나머지는 char형 예외라고 지정을 해놓았다.
- 위에 처럼 try~catch~catch 이런식으로 선언할수 있는데 역시나 마찬가지로 try와 catch 블록 사이에 다른 문장이 존재할 수 없다.
- #include <iostream>
- using namespace std;
- char* account="1234-4576";
int sid = 1122;
int balance = 1000; - class AccountExpt
{
char acc[10];
int sid; - public:
AccountExpt(char* str,int id)
{
strcpy(acc,str);
sid=id;
}
void What()
{
cout<<"계좌 : "<<acc<<endl;
cout<<"비번 : "<<sid<<endl;
} - };
- int main()
{
char acc[10];
int id;
int money; - cout<<"계좌번호 입력 :";
cin >> acc;
cout<<"비밀번호 입력 : ";
cin >> id;
if (strcmp(account,acc)||sid!=id)
throw AccountExpt(acc,id); - cout<<"출금액 입력 : ";
cin>>money; - if(balance<money)
throw money;
balance -=money;
cout<<"잔액 :"<<balance<<endl;
return 0;
}
- 위의 소스는 적절하게 예외처리를 안한 상태이다.
- #include <iostream>
- using namespace std;
- char* account="1234-4576";
int sid = 1122;
int balance = 1000; - class AccountExpt
{
char acc[10];
int sid; - public:
AccountExpt(char* str,int id)
{
strcpy(acc,str);
sid=id;
}
void What()
{
cout<<"계좌 : "<<acc<<endl;
cout<<"비번 : "<<sid<<endl;
} - };
- int main()
{
char acc[10];
int id;
int money; - try
{
cout<<"계좌번호 입력 :";
cin >> acc;
cout<<"비밀번호 입력 : ";
cin >> id;
if (strcmp(account,acc)||sid!=id)
throw AccountExpt(acc,id);
}
catch(AccountExpt& expt)
{
cout<<"다시 입력을 확인하세요"<<endl;
expt.What();
} - try
{ - cout<<"출금액 입력 : ";
cin>>money; - if(balance<money)
throw money;
balance -=money;
cout<<"잔액 :"<<balance<<endl;
}
catch(int money)
{
cout<<"부족 금액 :"<<money-balance<<endl;
} - return 0;
}
-
실행시켜 보면 알겠지만 예외처리는 해주었다고 하지만 엉망이다.
- 참고로 catch(AccountExpt& expt) 이렇게 한 이유는 객체가 복사되는 부담을 줄이기 위한 것이기 때문에 꼭 이렇게 할 필요는 없다.
- #include <iostream>
- using namespace std;
- char* account="1234-4576";
int sid = 1122;
int balance = 1000; - class AccountExpt
{
char acc[10];
int sid; - public:
AccountExpt(char* str,int id)
{
strcpy(acc,str);
sid=id;
}
void What()
{
cout<<"계좌 : "<<acc<<endl;
cout<<"비번 : "<<sid<<endl;
} - };
- int main()
{
char acc[10];
int id;
int money; - try
{
cout<<"계좌번호 입력 :";
cin >> acc;
cout<<"비밀번호 입력 : ";
cin >> id;
if (strcmp(account,acc)||sid!=id)
throw AccountExpt(acc,id);
cout<<"출금액 입력 : ";
cin>>money; - if(balance<money)
throw money;
balance -=money;
cout<<"잔액 :"<<balance<<endl;
}
catch(AccountExpt& expt)
{
cout<<"다시 입력을 확인하세요"<<endl;
expt.What();
}
catch(int money)
{
cout<<"부족 금액 :"<<money-balance<<endl;
} - return 0;
}
-
위와 같이 try 문에 다 집어 넣어야 정상적으로 된다.
- 이유는 앞전에서 봤을때 비밀번호가 이상하면 예외처리를 부르고 나서 또 다시 그다음에 이어서 실행이 되기때문이다.
IV. 예외 상황을 나타내는 클래스의 설계
-
예외를 발생시키기 위해서 클래스를 정의하고 객체를 생성하였다.
- 이러한 객체를 예외 객체라고 하며, 예외 객체를 위해 정의되는 글래스를 가리켜 예외 클래스라 한다.
V. 예외를 나타내는 클래스의 상속
-
catch 블록에 예외가 전달되는 방식
- 이어서 선언되어 있는 catch 블록에 예외가 전달되는 형태를 보면 함수 오버로딩과 유사하다.
- 단, 오버로딩된 함수는 딱 봐서 매개변수가 일치하는 함수가 호출되고 예외를 처리할 catch 블록은 위에서 부터 순차적으로 비교를 이루어지고나서 결정이 난다.
- 상속 관계에 있는 예외 객체의 전달
- #include <iostream>
- using namespace std;
- class ExceptA
{
public:
void What()
{
cout<<"ExceptA 예외"<<endl;
}
}; - class ExceptB:public ExceptA
{
public:
void What()
{
cout<<"ExceptB 예외"<<endl;
}
}; - class ExceptC : public ExceptB
{
public:
void What()
{
cout<<"ExceptC 예외"<<endl;
}
}; - void ExceptFunction(int ex)
{
if(ex == 1)
throw ExceptA();
else if(ex == 2)
throw ExceptB();
else
throw ExceptC();
} - int main()
{
int exID;
cout<<"발생시킬 예외의 숫자 : ";
cin>>exID;
try
{
ExceptFunction(exID);
}
catch(ExceptA& ex)
{
cout<<"catch(ExceptA& ex)에 의한 처리"<<endl;
ex.What();
}
catch(ExceptB& ex)
{
cout<<"catch(ExceptB& ex)에 의한 처리"<<endl;
ex.What();
}
catch(ExceptC& ex)
{
cout<<"catch(ExceptC& ex)에 의한 처리"<<endl;
ex.What();
}
return 0;
}
-
입력을 1,2,3을 해도 ExceptA에 의한 처리로만 나온다. 왜그럴까?
- 앞에서 말했다시피 catch는 순차적으로 비교를 한다. A를 먼저 비교 할텐데 1,2,3이 예외가 발생하면 각각의 catch에 가겠지만 상속을 받았기 때문에 A에서도 예외가 적용이 되어서 A로만 찍힌다.
- 다음과 같이 바꾸어 보자.
- #include <iostream>
- using namespace std;
- class ExceptA
{
public:
void What()
{
cout<<"ExceptA 예외"<<endl;
}
}; - class ExceptB:public ExceptA
{
public:
void What()
{
cout<<"ExceptB 예외"<<endl;
}
}; - class ExceptC : public ExceptB
{
public:
void What()
{
cout<<"ExceptC 예외"<<endl;
}
}; - void ExceptFunction(int ex)
{
if(ex == 1)
throw ExceptA();
else if(ex == 2)
throw ExceptB();
else
throw ExceptC();
} - int main()
{
int exID;
cout<<"발생시킬 예외의 숫자 : ";
cin>>exID;
try
{
ExceptFunction(exID);
}
catch(ExceptC& ex)
{
cout<<"catch(ExceptC& ex)에 의한 처리"<<endl;
ex.What();
}
catch(ExceptB& ex)
{
cout<<"catch(ExceptB& ex)에 의한 처리"<<endl;
ex.What();
}
catch(ExceptA& ex)
{
cout<<"catch(ExceptA& ex)에 의한 처리"<<endl;
ex.What();
}
return 0;
}
-
앞전의 소스와 달라진 점은 catch 블록의 순서가 달라졌다.
- 바꾼 이유는 IS-A관계는 역으로 성립하지 않음을 이용한 것이다. 즉, A 예외는 C예외가 아니다.
VI. new 연산자에 의해 전달되는 예외
-
new 연산자에 의해서 메모리 할당에 실패 했을 경우 NULL포인터가 리턴된다고 했었다.
- C++표준에서는 new 연산자가 메모리 할당에 실패했을 경우 bad_alloc 예외가 전달된다고 한다.
- 자세한건 MSDN을 참조하는게 좋다.
- #include <iostream>
#include <new> - using namespace std;
- int main()
{
try{
int i=0;
while(1)
{
cout<<i++<<"번째 할당"<<endl;
double(*arr)[10000] = new double[10000][10000];
} - }
catch(bad_alloc ex)
{
ex.what();
cout<<endl<<"End"<<endl;
}
return 0;
}
-
위의 소스를 실행 시켜 보면 무한루프를 돌면서 메모리 공간만 할당하고 있다.
- 어느 순간 new 연산이 실패로 돌아가면 bad_alloc 예외가 발생하고 END를 찍게된다.
VII. 예외처리에 대한 나머지 문법 요소
- 모든 예외를 처리하는 catch 블록
- try
- {
- }
- catch(...)
- {
- }
- 위에서 보면 "..." 의 선언은 모든 예외를 다 처리하겠다는 선언이다.(잘 사용하지는 않는다.)
- 예외 다시 던지기
- #include <iostream>
- using namespace std;
- class Exception
{
public:
void what()
{
cout<<"Simple Exception"<<endl;
}
}; - void ThrowException()
{
try
{
throw Exception();
}
catch(Exception& t)
{
t.what();
throw;
}
} - int main()
{
try
{
ThrowException();
}
catch(Exception& t)
{
t.what();
}
return 0;
}
-
먼저 ThrowException()에서 예외를 처리하고 catch에서 throw로 예외를 던졌다.
- 던져진 예외는 메인에 있는 ThrowException();으로 가고 그다음에 있는 catch에서 다시 한번 예외가 처리되어 Simple Exception이 2번 찍힌다.
-
다음과 같은 경우 예외를 다시 던질 것을 고려해 보자.
- catch 블록에 의해 예외를 잡고 보니, 처리하기가 마땅치 않다. 다른 catch블록에 의해서 예외가 처리되길바란다.
- 하나의 예외에 대해서 처리되어야 할 영역(예외가 발생했음을 알려줘야 할 영역)은 둘 이상이다.
이 글은 스프링노트에서 작성되었습니다.
'2. C/C++ > 02. C++' 카테고리의 다른 글
012. 템플릿(Template) (0) | 2008.12.07 |
---|---|
011. string 클래스 디자인 (0) | 2008.12.07 |
010. 연산자 오버로딩 (0) | 2008.12.07 |
009. virtual의 원리와 다중 상속 (0) | 2008.12.07 |
008. 상속과 다형성 (0) | 2008.12.07 |