티스토리 뷰

※ 주의 사항 ※

  • 이 글의 목적은 '지식의 전달'이 아닌 '학습의 기록'입니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.

 

지금부터 다루게 될 예외 처리 문법은 프로그램의 성능을 많이 잡아먹는다고 합니다. 그런 이유로 예외 처리 문법을 사용하기보다 if문을 이용하는 게 더 좋다고 합니다. 이런 방식의 예외 처리 방법이 있다는 것 정도로만 알아두시면 좋을 것 같습니다.


예외 처리에서의 '예외'는 프로그램 실행 도중에 발생하는 '예외적인 상황'을 의미합니다. 그리고 C++는 이러한 예외적인 상황의 처리를 위한 문법을 별도로 제공하고 있습니다.

 

예외

C++에서 말하는 '예외'는 프로그램의 실행 도중에 발생하는 문제 상황을 의미합니다. 따라서 컴파일 시 발생하는 문법적인 에러는 예외의 범주에 포함되지 않습니다. 몇 가지 예외상황을 예로 들면 다음과 같습니다.

  • 나이를 입력하라고 했는데, 0보다 작은 값이 입력되었다.
  • 나눗셈을 위한 두 개의 정수를 입력받는데, 나누는 수로 0이 입력되었다.
  • 주민등록번호 13자리만 입력하라고 했는데, 중간에 -를 포함하여 14자리를 입력하였다.

 

이렇듯 '예외'라는 것은 문법적인 오류가 아닌, 프로그램의 논리에 맞지 않는 상황을 의미합니다. 다음 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	cout << "나눗셈의 몫 : " << num1 / num2 << endl;
	cout << "나눗셈의 나머지 : " << num1 % num2 << endl;

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 2
나눗셈의 몫 : 4
나눗셈의 나머지 : 1

*/

 

위 예제에서 만약 두 번째 숫자로 0을 입력하면 다음의 실행결과까지만 보이고 프로그램이 강제 종료됩니다.

/*
실행결과

두 개의 숫자 입력 : 9 0

*/

 

예외 상황이 발생하면 그에 따른 처리가 이뤄져야지, 위의 실행 결과처럼 프로그램이 그냥 종료되어 버리는 상황을 만들어선 안됩니다.

 

if문을 이용한 예외 처리

우리가 알고 있는 예외의 처리는 if문을 이용하는 것입니다. 다음 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	if (num2 == 0)
	{
		cout << "제수는 0이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	else
	{
		cout << "나눗셈의 몫 : " << num1 / num2 << endl;
		cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
	}

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.

*/

 

if문을 이용해서 적절하게 예외 처리를 할 수 있지만 여기서 중요한 건 예외가 발생한 시점은 num2에 0을 입력받는 시점이라는 것입니다. if문을 이용한 예외처리는 num2에 0을 입력하는 시점이 아닌, 그 이후 num2의 값이 0인지 확인하는 시점부터 이뤄집니다. 이렇듯 '예외가 발생하는 위치'는 '예외가 발견되는 위치'와 다를 수 있습니다.

 

그리고 if문을 이용한 예외 처리 방식은, 이 if문이 논리적인 기능을 위해 사용되었는지, 아니면 예외처리를 위해 사용되었는지 얼핏 봐서는 구분할 수가 없다는 단점을 가지고 있습니다.

 

하지만 이어서 배우게 될 C++의 예외처리 메커니즘을 이용하면 이런 것이 가능해집니다.

 

예외 처리 메커니즘

C++는 구조적으로 예외를 처리할 수 있는 메커니즘을 제공합니다. 이 메커니즘을 이용하면, 코드의 가독성과 유지 보수성을 높일 수 있습니다.

 

예외 처리 메커니즘과 관련해서 익숙해져야 할 세 가지 키워드는 다음과 같습니다.

  • try
  • catch
  • throw

 

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

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

 

다음은 catch 블록에 대한 설명입니다. try블록에서 예외가 발생함을 감지하면 해당 예외 상황에 대한 예외처리를 catch블록에서 수행합니다.

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

 

사실 try와 catch는 하나의 문장입니다. 따라서 항상 try 뒤에 catch가 이어서 등장해야 하며,

try
{
	//예외 발생 예상 지역
}
catch(처리할 예외의 종류 명시ㅣ)
{
	//예외처리 코드 삽입
}

 

다음과 같이 중간에 다른 문장이 오면 안 됩니다.

try
{
	//예외 발생 예상 지역
}
cout << "Simple message" << endl;    //컴파일 에러 발생
catch(처리할 예외의 종류 명시ㅣ)
{
	//예외처리 코드 삽입
}

 

다음은 throw에 대한 설명입니다. try와 catch는 블록의 형태였지만, throw는 아닙니다. throw는 예외가 발생했음을 알리는 문장의 구성에 사용됩니다.

throw expn;

 

위의 문장에서 expn은 변수, 상수, 객체 등 표현 가능한 모든 데이터가 될 수 있지만, 예외 상황에 대한 정보를 담은, 의미 있는 데이터여야 합니다. 그래서 expn의 위치에 오는 데이터를 가리켜 그냥 '예외'라고 표현하는 것이 일반적입니다. 이 책의 저자는 이해하기 쉽게 '예외 데이터' 혹은 '예외 객체'라고 표현하기도 합니다.

 

try, catch, throw가 모두 사용되는 전체적인 구조는 다음과 같습니다.

try
{
	......
	if(예외가 발생한다면)
	{
		throw expn;    //예외의 발생
	}
}
catch(type expn)       //예외 expn 전달
{
	//예외처리 코드 삽입
}

 

try블록이 예외를 감지한다는 것은 바로 throw 키워드의 실행을 감지한다는 것입니다. try블록 내에서 예외상황의 유무를 판별하기 위해 if문이 삽입되었고, 해당 if문 내에서 예외상황이 발견되었다면 throw expn;을 실행합니다. 그리고 try블록 내에서 throw 키워드가 실행되는 것이 감지되면 throw가 가리키는 예외 데이터인 expn을 catch블록의 매개변수(?)에 전달합니다. 그리고 catch 블록에서 이에 맞는 예외처리를 수행하게 됩니다.

 

(위 구조를 보면서 아래에 보일 이런 구조도 가능할 수 있겠다는 생각이 들었습니다. 이에 대한 개념이 이후 나오는지 봐야겠습니다.) 

try
{
	......
	if(예외가 발생한다면)
	{
		throw expn1;    
	}
    
	if(예외가 발생한다면)
	{
		throw expn2;    
	}
    
	if(예외가 발생한다면)
	{
		throw expn3;    
	}
}
catch(type expn1)     
{
	//예외처리 코드 삽입
}
catch(type expn2)     
{
	//예외처리 코드 삽입
}
catch(type expn3)     
{
	//예외처리 코드 삽입
}

 

기본적인 C++의 예외 처리 문법을 살펴봤으니, 이를 기반으로 만든 다음의 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	try
	{
		if (num2 == 0) throw num2;
		cout << "나눗셈의 몫 : " << num1 / num2 << endl;
		cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
	}
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	cout << "end of main" << endl;

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
end of main

*/

 

위 예제를 보면 한 가지 특이한 점을 확인할 수 있습니다. 먼저, 다음의 문장을 보겠습니다.

try
{
	if (num2 == 0) throw num2;
	cout << "나눗셈의 몫 : " << num1 / num2 << endl;
	cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
}

 

위 문장에서 num2가 0이라는 것이 발견되었고, throw num2; 코드를 읽어냈습니다. 그리고 이를 try블록에서 감지했습니다. 그러자 그 아래의 두 줄의 출력 문장은 실행되지 않았습니다.

 

이처럼 try블록 안에서 예외가 발생함이 감지되면, 그 시점부터 try블록 내 나머지 코드의 진행이 멈추고, 예외처리 메커니즘이 실행됩니다. 따라서 예외 처리를 위해 catch블록이 실행되고, catch블록 내의 코드들이 실행되었습니다. 이후 프로그램은 catch 블록 이후의 나머지 코드들을 읽어 내려갑니다.

 

그리고 throw에 의해 던져지는 예외 데이터의 자료형과 catch블록의 매개 변수 자료형은 동일해야 합니다. 만약 그렇지 않으면, 던져진 예외 데이터는 catch블록으로 전달되지 않습니다.

 

이제 try블록을 묶는 기준이 어떻게 되는지 설명하겠습니다. 앞선 예제를 다시 보이겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	try
	{
		if (num2 == 0) throw num2;
		cout << "나눗셈의 몫 : " << num1 / num2 << endl;
		cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
	}
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	cout << "end of main" << endl;

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
end of main

*/

 

이 예제를 다음과 같이 수정한다면 어떻게 될까요?

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	try
	{
		if (num2 == 0) throw num2;
	}
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
    
	cout << "나눗셈의 몫 : " << num1 / num2 << endl;
	cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
    
	cout << "end of main" << endl;

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.

*/

 

위와 같이 문장을 구성해도 예외를 발견하고 예외 처리하는 데에는 문제가 없습니다. 그런데 그다음 num1을 num2로 나누는 연산을 수행하게 됩니다. 이에 프로그램은 해당 연산을 할 수 없어 강제 종료됩니다. 그래서 end of main 문구가 출력되지 않았습니다.

 

위 예로 try블록을 어디까지 묶어야 하는지 대략 눈치챘을 것 같습니다. try블록 내에서 예외가 감지되면, 그 시점부터 try블록 내 나머지 코드들은 실행되지 않습니다. 바로 이것을 이용해야 합니다. 즉, 해당 예외가 발생할 경우 읽어선 안 되는 코드들까지 같은 try블록에 묶어주어야 합니다.

 

Stack Unwinding(스택 풀기)

MyFunc이라는 함수를 호출했습니다. 그런데 그 안에서 throw절이 실행되면서 예외가 발생했습니다. 그런데 이 함수 내에서는 예외 처리를 위한 try~catch문이 존재하지 않습니다. 그렇다면, 이 상황에서 발생한 예외는 어떻게 처리될까요? 저는 발생한 예외가 처리되지 않고 그대로 문제가 있는 코드를 읽어갈 것이라고 생각했습니다. 그런데 그렇지 않았습니다.

 

위와 같은 경우 발생한 예외 처리의 책임은 해당 함수를 호출한 영역으로 넘어갑니다. 즉, main함수 내에서 MyFunc함수를 호출했다고 가정해 보겠습니다. MyFunc 안에서 예외가 발생했으나 MyFunc함수 안에서 이를 처리하지 않으면, 해당 예외 처리의 책임을 MyFunc함수를 호출한 main함수에게 묻습니다. 따라서 main 함수에서 try~catch문을 이용하여 예외처리를 해주어야 합니다.

 

다음의 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

void Divide(int num1, int num2)
{
	if (num2 == 0) throw num2;
	cout << "나눗셈의 몫 : " << num1 / num2 << endl;
	cout << "나눗셈의 나머지 : " << num1 % num2 << endl;
}

int main(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력 : ";
	cin >> num1 >> num2;

	try
	{
		Divide(num1, num2);
	}
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	cout << "end of main" << endl;

	return 0;
}

/*
실행결과

두 개의 숫자 입력 : 9 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
end of main

*/

 

위 예제를 보면 Divide 함수 내에서 throw 키워드의 사용으로 예외는 감지했지만, 해당 예외를 처리하기 위한 try~catch문은 보이지 않습니다. 이렇게 되면 해당 함수에서 예외가 발견된 시점부터 함수 내의 나머지 코드들을 읽지 않고 바로 그 함수를 빠져나와 예외의 발생을 알립니다.

 

해당 함수를 호출한 main 함수 내에 try~catch문이 사용되었고, try블록 안에 Divide함수를 호출하는 문장이 있으므로 try블록에서 Divide 함수가 알리는 예외를 감지할 수 있습니다. 이렇게 해서 예외처리 메커니즘이 동작하게 됩니다.

 

예외가 처리되지 않아서, 함수를 호출한 영역으로 예외 데이터가 전달되는 현상을 가리켜 '스택 풀기'라고 합니다. 

 

만약 어느 함수 내에서 예외 처리가 되지 않아서, 예외 처리에 대한 책임이 main함수에까지 도달했는데, main 함수마저 예외 처리를 하지 않으면 terminate함수(프로그램을 종료시키는 함수)가 호출되면서 프로그램이 종료됩니다. 따라서 시스템 오류로 인해서 발생한 예외 상황이 아니라면, 더 이상 프로그램의 실행이 불가능한 예외 상황이 아니라면, 반드시 프로그래머가 예외 상황을 처리해야 합니다.

 

둘 이상의 catch 블록

하나의 try블록 내에서 유형이 다른 둘 이상의 예외 상황이 발생할 수 있고, 이러한 경우 각각의 예외를 표현하기 위해 사용되는 예외 데이터의 자료형이 다를 수 있습니다. 때문에 try블록에 이어서 등장하는 catct블록은 둘 이상이 될 수 있습니다. 다음의 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
using namespace std;

int main(void)
{
	int num;
	cout << "숫자 입력 : ";
	cin >> num;

	try
	{
		if (num < 100) throw - 1;
		else throw 'c';
        
        cout << "예외가 발생할 수 밖에 없는 문장" << endl;
	}
	catch (int expn)
	{
		cout << "catch int expn : " << expn << endl;
	}
	catch (char expn)
	{
		cout << "catch char expn : " << expn << endl;
	}

	cout << "end of main" << endl;

	return 0;
}

/*
실행결과

숫자 입력 : 120
catch char expn : c
end of main

*/

 

위 예제를 보면 예외가 발생할 수밖에 없는 상황입니다. 만약 num가 100 미만이라면, throw는 -1을 예외 데이터로 전달할 것이고, num가 100 이상이라면 throw는 'c'를 예외 데이터로 전달할 것입니다. 그런데 -1과 'c'는 자료형이 각각 int와 char로 다릅니다. 그래서 해당 예외 데이터를 받기 위해 catch문이 두 개로 정의되어 있습니다.

 

함수 내에서 발생할 수 있는 예외의 종류도 함수의 특징으로 간주됩니다. 따라서 이미 정의된 특정 함수의 호출을 위해서는 함수의 이름, 매개 변수의 선언, 반환형 정보에 더해서 함수 내에서 전달될 수 있는 예외의 종류와 그 상황도 알아야 합니다. 그래야 함수의 호출 문장을 감싸는 적절한 try~catch문을 구성할 수 있기 때문입니다.

 

따라서  함수를 정의할 때에는 함수 내에서 발생 가능한 예외의 종류를 다음과 같이 명시해 주는 것이 좋습니다.

int TrowFunc(int num) throw(int, char)
{
	......
}

 

위의 throw선언은 해당 함수 내에서 예외상황의 발생으로 인해서 int형 예외 데이터와 char형 예외 데이터가 전달될 수 있음을 알리는 의미를 가지고 있습니다.

 

예외 상황을 표현하는 예외 클래스 설계

지금까지는 기본 자료형 데이터만을 예외 데이터로 사용했습니다. 하지만 클래스의 객체도 예외 데이터가 될 수 있고 또 이것이 일반적입니다.

 

예외 발생을 알리는 데 사용되는 객체를 가리켜 '예외 객체'라고 하고, 예외 객체의 생성을 위해 정의된 클래스를 '예외 클래스'라고 합니다. 객체를 이용해서 예외 상황을 알리면, 예외가 발생한 원인에 대한 정보를 보다 자세히 담을 수 있습니다.

 

다음의 예제는 현금 인출기에서의 돈 인출 과정을 간단히 시뮬레이션한 것입니다.

//main.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;
	}
};

class Account
{
private:
	char accNum[50];    //계좌번호
	int balance;        //잔고
public:
	Account(const 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 ShowMoney()
	{
		cout << "잔고 : " << balance << endl << endl;
	}
};

int main(void)
{
	Account acc("56789-827120", 5000);

	try
	{
		acc.Deposit(2000);
		acc.Deposit(-300);
	}
	catch (DepositException expn)
	{
		expn.ShowExceptionReason();
	}
	acc.ShowMoney();

	try
	{
		acc.Withdraw(3500);
		acc.Withdraw(4500);
	}
	catch (WithdrawException expn)
	{
		expn.ShowExceptionReason();
	}
	acc.ShowMoney();

	return 0;
}

/*
실행결과

[예외 메시지 : -300는 입금 불가]
잔고 : 7000

[예외 메시지 : 잔액 3500, 잔액 부족]
잔고 : 3500

*/

 

위 예제와 같이 예외 클래스라고 특별히 다른 것은 없습니다. 다만, 예외 상황을 잘 표현할 수 있을 정도로만 정의하면 됩니다. 너무 복잡하게 정의할 필요도 없습니다.

 

예외 클래스 상속

예외 클래스도 상속 관계를 구성할 수 있습니다. 앞선 예제에서 보인 두 예외 클래스는 다음과 같이 상속의 관계로 묶을 수 있습니다.

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;
	}
};

 

그리고 다음과 같이 예외처리를 단순화할 수 있습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
#include <cstring>
using namespace std;

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;
	}
};

class Account
{
private:
	char accNum[50];    //계좌번호
	int balance;        //잔고
public:
	Account(const char* acc, int money) : balance(money)
	{
		strcpy(accNum, acc);
	}
	void Deposit(int money) throw(AccountException)
	{
		if (money < 0)
		{
			DepositException expn(money);
			throw expn;
		}
		balance += money;
	}
	void Withdraw(int money) throw(AccountException)
	{
		if (money > balance)
		{
			throw WithdrawException(balance);
		}
		balance -= money;
	}
	void ShowMoney()
	{
		cout << "잔고 : " << balance << endl << endl;
	}
};

int main(void)
{
	Account acc("56789-827120", 5000);

	try
	{
		acc.Deposit(2000);
		acc.Deposit(-300);
	}
	catch (AccountException& expn)    //참조형으로 예외 데이터를 받음
	{
		expn.ShowExceptionReason();
	}
	acc.ShowMoney();

	try
	{
		acc.Withdraw(3500);
		acc.Withdraw(4500);
	}
	catch (AccountException& expn)    //참조형으로 예외 데이터를 받음
	{
		expn.ShowExceptionReason();
	}
	acc.ShowMoney();

	return 0;
}

/*
실행결과는 동일합니다.
*/

 

위 예제에서 주의할 점은 catch문에서 예외 객체를 받을 때 참조형으로 받았다는 것입니다. 

 

예외 처리와 관련된 또 다른 특성들

라이브러리에 선언된 예외 클래스

new 연산에 의한 메모리 공간의 할당이 실패하면 bad_alloc이라는 예외가 발생합니다. bad_alloc은 헤더 파일 <new>에 선언된 예외 클래스로서 메모리 공간의 할당이 실패했음을 알리는 의도로 정의되었습니다. 다음 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
#include <new>
using namespace std;

int main(void)
{
	int num = 0;
	try
	{
		while (1)
		{
			num++;
			cout << num << "번째 할당 시도" << endl;
			new double[10000][10000];
		}
	}
	catch (bad_alloc & expn)
	{
		cout << expn.what() << endl;
		cout << "더 이상 할당 불가" << endl;
	}

	return 0;
}

/*
실행결과

1번째 할당 시도
2번째 할당 시도
3번째 할당 시도
bad allocation
더 이상 할당 불가

*/

 

이렇듯 프로그래머가 직접 정의하지 않아도 발생하는 예외도 있습니다.

 

모든 예외를 처리하는 catch 블록

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

try
{
	......
}
catch(...)    //...은 전달되는 모든 예외를 받아주겠다는 선언
{
	......
}

 

이런 특성으로 인해 마지막 catch블록으로 덧붙여지는 경우가 많습니다.

 

catch 블록에서 예외 throw

catch블록에 전달된 예외는 다시 던져질 수 있습니다. 그리고 이로 인해서 하나의 예외가 둘 이상의 catch블록에 의해서 처리되게 할 수 있습니다. 다음 예제를 보겠습니다.

//main.cpp 소스 파일로 저장
#include <iostream>
#include <new>
using namespace std;

void Divide(int num1, int num2)
{
	try
	{
		if (num2 == 0)
		{
			throw 0;
			cout << "몫 : " << num1 / num2 << endl;
			cout << "나머지 : " << num1 % num2 << endl;
		}
	}
	catch (int expn)
	{
		cout << "first catch" << endl;
		throw;    //예외를 다시 던지다
	}
}

int main(void)
{
	try
	{
		Divide(9, 2);
		Divide(4, 0);
	}
	catch (int expn)
	{
		cout << "second catch" << endl;
	}

	return 0;
}

/*
실행결과

first catch
second catch

*/

위 코드를 보면 Divide 함수의 catch블록 안에, throw 키워드를 사용하였는데 아무것도 작성되어 있지 않습니다. 아무런 예외 데이터도 던지지 않는 것인지 생각할 수 있지만 그런 문법은 에러를 발생합니다.

 

책에서도 아무런 설명이 없습니다. 따라서 저는 이렇게 생각합니다. 한 번 받았던 예외 데이터를 catch에서 다시 던지는 것으로, 아무런 명시를 하지 않아도 해당 catch블록이 처음 받았던 예외 데이터를 다시 던지는 것이라고 생각합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함