본문 바로가기
스터디/C++

[ C++ ] 예외처리 메커니즘( try, catch, throw ) 총 정리

by 알 수 없는 사용자 2019. 12. 2.

우리에게 익숙한 예외처리는 if문을 이용한 예외처리이다. 하지만 if문을 보고 예외처리를 위한 코드인지 프로그램의 흐름을 구성하는 코드인지 쉽게 구분하지 못해서 가독성이 떨어진다.

 

하지만 C++의 예외처리 메커니즘을 이용하면 가독성을 높일 수 있다.

C++은 구조적으로 예외를 처리할 수 있는 메커니즘을 제공한다. 가독성유지보수성을 높일 수 있을 뿐만 아니라, 예외의 처리를 프로그램의 일반적인 흐름에서 독립시키는 것이 가능하다.

 

  • try

try
{
	// 예외발생 예상지역
}

    try 블록은 예외 발생에 대한 검사의 범위를 지정할 때 사용된다. 즉, try 블록 내에서 예외가 발생하면, C++의 예외처리 메커니즘에 의해서 처리가 된다.

 

  • catch 

catch(처리할 예외의 종류 명시)
{
	// 예외처리 코드의 삽입
}

    catch 블록은 try 블록에서 발생한 예외를 처리하는 코드가 담기는 영역으로써, 그 형태가 마치 반환형이 없는 함수와 유사하다.

 

catch 블록은 항상 try 블록의 뒤에 이어서 등장해야 하며, try 블록에서 발생한 예외는 catch 블록에서 처리된다.

try
{
	// 예외발생 예상 지역
}
cout<< "Hello world!" <<endl; // 컴파일 error 
catch(처리할 예외의 종류 명시)
{
	// 예외처리 코드의 삽입
}

 

  • throw

    throw는 예외가 발생했음을 알리는 문장의 구성에 사용된다.

 

throw expn;

 

위의 문장에서 expn은 변수, 상수 그리고 객체 등 표현 가능한 모든 데이터가 될 수 있으나, 예외상황에 대한 정보를 담은, 의미 있는 데이터이어야 한다. 그래서 expn의 위치에 오는 데이터를 가리켜 '예외'라고 표현한다.

 

 

또한 위의 문장이 실행되면 C++의 예외처리 메커니즘이 동작한다.

"throw에 의해 던져진 '예외' 데이터는, '예외' 데이터를 감싸는 try 블록에 의해서 감지가 되어 이어서 등장하는 catch 블록에 의해 처리된다."

 

try
{

	if(예외가 발생한다면)
    		throw expn;

}
catch (type expn)
{
	// 예외의 처리
}

 

예제를 보자.

두 숫자를 입력하면 두 숫자를 나눠서 몫과 나머지를 출력하는 프로그램이다.

근데 제수가 0일 경우에는 예외가 발생하기에 예외처리를 작성하였다.

 

n2가 0이 되면 throw를 통해 n2 값이 catch 블록으로 던져진다.

즉, try 블록 내에서 예외가 발생하면, catch 블록이 실행되고 나서, 예외가 발생한 지점 이후에 실행하는 것이 아니라, catch 블록의 이후가 실행된다.

 

그리고 throw 절에 의해 던져진 '예외' 데이터의 자료형과 catch 블록의 매개변수 자료형은 일치해야 한다.

만약에 일치하지 않으면, 던져진 '예외' 데이터는 catch 블록으로 전달되지 않는다.

그럼 '예외' 데이터가 catch 블록으로 전달되지 않은 경우는 어떻게 될까?

그때는 terminate 함수의 호출로 인해서 프로그램은 종료된다. ( 주의해서 최대한 예외처리를 하자. )

 

그럼 하나의 try 블록 내에서 다른 둘 이상의 예외상황이 발생하면 어떻게 해야 할까?

이런 경우에는 각각의 예외를 표현하기 위해 사용되는 예외 데이터의 자료형이 다를 수 이기 때문에,

catch 블록은 둘 이상이 될 수 있다.

 

  • 다중 catch 문

다음은 위의 예제를 더 구체화한 것이다.

Chk 메서드는 char 배열을 num으로 바꿔 주는 함수이다.

01, 012와 같이 0으로 시작하는 수의 입력에 정수형 예외를 던져준다.

str 배열의 길이만큼 for문을 돌면서 숫자가 아닌 문자가 들어왔을 경우에는 문자형 예외를 던져준다.

이때 예외는 함수를 통해서 던져줄 수 있다. ( 메서드가 try 블록 내에서 호출되어야 한다. )

 

그리고 pow를 통해서 1의 자리, 10의 자리, 100의 자리만큼 10의 배수를 곱해주고,

문자에 '0'을 빼줌으로 써 숫자 값을 얻도록 한다. 이건 아스키코드표를 참고하면 된다.

( '0'은 48번이고, 숫자 0은 0번이다. )

 

메인 함수이다.  while문을 통해서 try가 정상적으로 종료가 되지 않고 catch로 예외가 던져질 경우에 다시 루프를 돌도록 처리되어있다. 보면 char형 데이터의 예외처리int형 데이터의 예외처리가 된 것을 알 수 있다.

 

예외처리가 잘 된 것을 확인할 수 있다.

 

함수 내에서 발생할 수 있는 예외의 종류도 함수의 특징으로 간주된다. 따라서 이미 정의된 특정 함수의 호출을 위해서는 함수의 이름, 매개변수 선언, 반환형 정보에 더해서, 함수 내에서 전달될 수 있는 예외의 종류(자료형)와 상황도 알아야 한다.

throw는 예외의 발생을 알리는 문장임을 잊지 말자.

 

* 다중 catch문의 주의점

catch문은 위에서부터 아래로 내려가면서 '예외' 데이터에 타당한 catch문을 찾는다.

근데 만약 유도 클래스의 자료형이 catch문 가장 위에 있고, 아래에 기초 클래스의 자료형이 밑에 있다고 가정하자.

그러면 기초클래스 자료형은 기초 클래스의 자료형인 catch문에 들어가지 않고 맨 위에 있는 유도 클래스의 자료형인 catch문에 들어가게 될 것이다. 

그러니 상속 구조의 클래스 자료형을 둘 이상 catch문에 기입하게 되면 기초 -> 유도 순서대로 catch문을 기입하자.

 

class A
{ ... };

class B : public A
{ ... };

class C : public B
{ ... };

int main()
{
	try
    { ... }
    catch(C& expn)
    { ... }
    catch(B& expn)
    { ... }
    catch(A& expn)
    { ... }
}

 

  • try 블록을 묶는 기준

 

try 블록을 묶는 기준은 예외가 발생할만한 영역을 묶는 것이 아니다!

예외가 발생할만한 영역만 묶는 것이 아니라, 그와 관련된 모든 문장을 함께 묶어서 이를 하나의 '일(work)'의 단위로 구성해야 한다.

 

만약에 다음과 같이 try 영역을 묶었다고 생각해보자.

그럼 예외가 발생했음에도 실행해서는 안 될 문장을 실행하는 꼴이 된다. 따라서 try 블록 안에 예외가 발생할 영역에 관련된 모든 문장을 같이 기입해야 한다.

 

 

  • catch ( ... )에 대하여

try
{
	...
}
catch(...)
{
	...
}

다음과 같이 catch 블럭을 선언하면, try 블록 내에서 전달되는 모드 예외가 자료형에 상관없이 걸려든다.

따라서 마지막 catch 블록에 많이 덧붙여지지만, 발생한 예외에 관련해서 정보를 얻을 수 없다.

 

 

  • catch 블럭에서 예외 던지기

20행, catch 블록 내에서 throw 하면 이전 스택에 쌓여있었던 main으로 예외를 던질 수 있다.

즉, 하나의 예외가 둘 이상의 catch 블럭에서 처리가 가능하다.