C++: try throw/catch
on
C++의 C에 대한 장점 중 하나가 exceptional case에 대한 대응 방법이 아닐까 한다. 즉, C에서 예외적인 경우가 발생하면 프로그램이 그냥 죽든가 오작동을 하게 되는데, C++에서는 이것에 대해서 별도의 대처가 가능하다는 이야기이다.
이것을 가능하게 해주는 것이 try/throw/catch이다. 물론 C++ 말고도 다른 객체지향적 언어가 이것을 지원한다.
try는 말 그대로 중괄호 안에 있는 내용을 한번 해보는 것이다. 문제가 생기면 (예외 상황이 생기면) catch로 연결된다.
throw는 catch를 부르는 것이다. 즉, 예외가 발생했음을 알린다.
catch는 예외상황의 handler이다. 예외 상황이 생겼을 때 대처하는 코드가 들어가게 된다.
쉬운 예를 들면
#include <iostream>
using namespace std;
double division(int a, int b) {
if( b == 0 ) {
throw "Division by zero condition!";
}
return (a/b);
}
int main () {
int x = 50;
int y = 1;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
쉬운 예이지만 필요한 내용은 모두 담고 있다. 0으로 나눴을 때 일반적으로 프로그램은 비정상적으로 crash한다. 반면 이 프로그램은 적절한 대처 (왜 문제가 생겼는지)를 하고 정상적으로 프로그램을 종료하게 된다.
읽어보면 division 함수를 부르는 일을 일단 try한다. division 함수에서는 0으로 나눌 때 throw를 한다. throw를 하면 이 때 catch에서 받게 된다.
즉, 나누는 일을 일단 try해보고 별 일 없으면 실행이 종료되지만, 0으로 나누게 되면 throw에서 0으로 나눴다는 메시지를 던지게 되고, 그 다음 catch에서 그 메시지를 받아서 메시지를 내보내는 일을 수행하고 프로그램이 종료하게 된다.
이 과정을 모두 생략하면 0으로 나눴을 땐 0으로 나눴다며 fatal error를 내며 프로그램이 비정상 종료되는 반면, 이렇게 하면 error message를 내보내고 정상적(?)으로 종료하게 된다.
exceptional case를 다루는 이유는 그 때문이다. 비정상적인 종료를 막기 위함이다. 프로그램을 작성하면서 불안정한 부분은 존재하게 마련이다. 작성하는 동안에도 알 수 있다. 이 부분이 나중에 문제를 일으킬지 아닐지 말이다.
아주 흔하게 메모리를 할당하는 부분이라든가 할당한 메모리에 접근하는 과정에서 예상치 않은 예외상황이 발생할 수 있다. 이런 흔한 예외상황에 대해서는 C++ standard library에서 별도로 정의를 하고 있을 정도로 잘 대처하고 있다. 만일 이런 문제가 생길 때마다 덩치가 큰 프로그램을 또는 벌려놓은 일이라든가 작업중인 파일을 제대로 닫지 못하고 비정상적으로 종료하는 것 보다는 예외 상황이 어떤 것인지 알리고 진행하던 작업 혹은 파일들을 최대한 잘 보전해놓고 종료하는 것이 좋다.
그런 의미에서 이 코드를 사용한다. 그러나 try/throw/catch를 친절하게 잘 다뤄놓은 소프트는 많이 보지 못했다. 다들 촉박하게 개발을 했는지 아예 이러한 exception에 대한 handling을 해본 적이 없든지 해서일 것이다.
솔직히 exceptional case에 대한 대처가 잘 되어있는 software와 그렇지 않은 software의 차이는 exceptional case가 빈번히 일어나야 알 수 있기 때문에 exceptional case의 발생을 전혀 인정하지 않는다거나 있을 수 없다 생각하면 이런 배려를 해둘 이유도 없을 수 있다.
대처를 해놓든 안 해놓든, 프로그램에서 정해놓은 일을 정상적으로 수행하지 못했다면 다음 작업으로 진행할 수가 없는 경우가 많아서 어떻게든 ‘망’한 것이니까 crash해서 망하든 친절하게 상황을 정리해주고 망하든 망한 것이다.
요약해보자면,
- try-catch, throw로 예외처리를 하도록 했다고 해서 crash가 되지 않는 것은 아니다. 단지 crash가 덜 일어나게 한다거나 일어난다해도 어떠한 예외상황이 발생한 것인지 알려준다거나 최악의 상황(crash가 되면서 사용자 데이터도 다 날려먹고 무슨 이유인지 전혀 알 수 없는 상황)을 피하는 코드를 만들 수 있다.
- 문제를 일으킬 만한 코드를 실행해야 할 때는 try 안에 넣고 실행한다.
- 문제가 될만한 코드는 예상되는 경우에 따라서 (crash의) 위험이 있는 경우에 대해서 예외 처리 코드를 삽입하고 throw를 통해서 어떠한 예외 경우가 발생했는지 알려주게 한다.
- catch에서는 이때의 에러 코드 (혹은 메시지, 혹은 시그널)를 받아서 그에 맞게 처리하게 한다.