본문 바로가기
프로그래밍/C++

예외 처리(try, catch, throw)

by 리뷰하는 (게임)프로그래머_리프TV 2010. 4. 4.



코드에서 예외 처리를 하는 방법은 많이 존재한다.

주로 조건문을 사용해서 예외 처리를 하게 되지만,

C++에서 예외 처리 메커니즘인 try, catch, throw문에 대해서 살펴 보도록 하자.

순서는 try안에서 throw를 해주면

catch에서 예외를 처리 한다고 보면 된다.

예제는 다음과 같다.

#include <iostream>

int divide( int a, int b );

void main()
{
	int a, b;

	std::cout << "두 개의 숫자 입력 : " << std::endl;
	std::cin >> a >> b;

	try
	{
		// 예외가 발생하면,
		if( b == 0 )
			throw b;	// throw로 넘겨주면,
		std::cout << " a/b의 몫 : " << divide(a,b) << std::endl;
	}
	catch(int exception)	// 여기서 예외를 실행한다.
	{
		std::cout << exception << " 입력 . " << std::endl;
		std::cout << "입력 오류 ! 다시 실행 하세요. " << std::endl;
	}
}

int divide( int a, int b )
{
	if( b == 0 )
		throw b;
	return a/b;
}​


구문 자체가 어색하게 느껴질 수도 있지만, 사용법이 어려운것은 아니다.
예외가 발생하면 throw b를 통해 catch에서 b를 인자로 받게 되고,
그 밑에 예외 처리문을 실행하게 되는 것이다.
단지 특이한 점은 예외 처리가 일어 나면 throw를 기준으로 그 밑 라인들은 실행을 하지 않는 다는 것이다.
이런 상황을 Stack Unwindung(스택 풀기)라고 한다.
좀더 상황을 만들면 다음과 같다.

#include <iostream>

int divide( int a, int b)
{
	if( b == 0 )
		throw b;	// 호출되는 함수 안에서 throw가 존재한다.
	// 문제가 생기면 스택을 빠져 나와 main으로 이동
	// 그 후에 try문을 찾는다.
	// 함수에서 또 함수를 부르고 또 다시 함수를 불러도
	// throw문이 발동하면 하나씩 빠져 나와 최종적으로
	// try문을 찾아 catch를 수행한다.
	return a/b;
}

void main()
{
	int a, b;

	std::cin >> a >> b;

	try
	{
		// try안에 throw는 없다.
		std::cout << divide( a, b ) << std::endl;
	}
	catch( int excption )
	{
		// 예외가 생기면 실행
		std::cout << excption << "입력" << std::endl;
	}
}​


이 처럼 예외가 전달되는 과정이 함수의 스택이 풀리는 순서와 일치 한다.
하지만 만약 try 나 catch가 없다면 어떻게 될까?
당연히 오류 메시지를 출력하고 프로그램을 강제 종료 하게 된다.
어떤식으로 메시지가 뜨는지 확인 하고 싶다면,
stdlib.h를 추가해서,
abort();
를 실행해 보자.


이런 식의 메시지를 확인 할 수 있을 것이다.
또 한가지 주의점은 catch에서 받는 매개변수의 타입이다.
catch( int excption ) 다음 처럼 int 형의 예외를 처리하는 catch문에을,
catch( char excption )로 변경한다면,
예외가 발생했을 때 그것을 처리하지 못하게 된다.
즉 abort가 또 실행된다.
그리고 가독성을 위해서 함수 옆에 throw가 발생 할 수 있는 예외에 대해서 설정할 수가 있는데,

int f( int a ) throw( int ) // int에 대한 throw문이 존재합니다.
int f( int a ) throw( int, double, char* )  // int나 double, char*의 throw문이 존재합니다.
int f( int a ) throw(  )  // 어떤 예외도 전달하지 않습니다.​

처럼 함수 내부를 다 찾아서 throw를 찾지 않아도,
함수 처음 부분만 보고도 어떤 예외가 돌아올 수 있는지 파악하여,
미리 catch문을 작성할 수 있게 도와준다고 한다.
throw에 대해서 클래스를 사용할 수도 있는데,
다음 예제를 보자.

#include <iostream>

#define PASSWORD 1234

class PasswordExpt
{
	int password;
public:
	PasswordExpt( int _pass ) : password(_pass){}
	void what()
	{
		std::cout << "비밀번호가 틀립니다." << std::endl;
		std::cout << password << "라고 입력" << std::endl; 
	}
};

void main()
{
	int money = 10000;
	int balance;
	int password;

	try 
	{
		std::cin >> password;
		// 비밀 번호가 틀리거나
		if( PASSWORD != password )
			throw PasswordExpt(password);

		// 출금액이 부족하면,
		std::cin >> balance;
		if( balance > money )
			throw money;
		// 모든 예외에서 넘어 가면 출금
		money -= balance;
	}

	// int 를 받는 catch
	catch( int _money )
	{
		std::cout << "잔금이 부족합니다. 현재 잔금은" << _money << std::endl;
	}
	// PasswordExpt를 받는 catch
	catch( PasswordExpt& expt )
	{
		expt.what();
	}
}​


따로 출력 결과를 표현하지는 않겠다. 여러 상황이 나올 수 있으니까,
한가지 확실한건 클래스를 사용해서 예외처리시에 인자로 넘겨 주었다는 것이다.
주의 할 점은 catch문은 받아 오는 인자에 따라 오버로딩 처럼 사용할 수 있지만,
실제로는 if처럼 순차적으로 검사한다는 것을 알아야 한다.
만약 AAA<-BBB<-CCC 라는 식으로 상속 구조가 되어 있다면
catch( AAA& a ) 안에서 모두다 허용할 수 있따는 것이다.
만약 이와 같은 구조를 피하고 싶다면.
catch( AAA& a );
catch( BBB& a );
catch( CCC& a );가 아닌

catch( CCC& a );
catch( BBB& a );
catch( AAA& a );로 해야 할 것이다.