본문 바로가기

Programming/C++ 3

[C++] 15-01. 예외처리

반응형

예외상황과 예외처리의 이해

 

문법적 오류가 아닌, 프로그램 논리에 맞지 않은 오류를 뜻한다. [프로그램 실행중 발생문제]

 

발생 -> 발견 -> 처리

 

#include<iostream>
using namespace std;

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

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

num2가 0이 입력되는 경우 논리 오류 발생

 

#include<iostream>
using namespace std;

int main_1(void)
{
	int num1, num2;
	cout << "두 개의 숫자 입력: ";
	cin >> num1 >> num2;								//1. num2에 0이 입력되면 예외가 발생

	if(num2==0)					
	{
		cout << "제수는 0이 될 수 없습니다. " << endl;	//2. 예외 발견
		cout << "프로그램을 다시 실행하세요." << endl;	//3. 예외처리 [다시 입력하라는 코드 출력]
	}
	else {
	cout << "나눗셈의 몫: " << num1 / num2 << endl;
	cout << "나눗셈의 나머지: " << num1 % num2 << endl;
}
	return 0;
}

if문을 이용한 예외처리 [예외를 해결하는 방법이 아니라 다시 입력을 유도하는 코드 출력]

 

C++의 예외처리 메커니즘의 필요성 대두 : 예외처리와 일반적인 프로그램 흐름 구분을 위함


 

 

C++의 예외처리 메커니즘 [try~catch, throw]

 

#include <iostream>
using namespace std;

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

	try {
		if (num2 == 0)
			throw num2;		//try문에 항상 존재하진 않지만, throw 절이 실행이되면 try블록의 나머지 부분은 실행이 되지 않는다.

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

	}						//예외와 연관이 있는 부분을 모두 하나의 try 블록으로 묶는다. [예외 발생시 실행되지 않도록]
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	cout << "end of main" << endl;	//예외 발생하지 않으면 출력되는 문장
	return 0;
}

try문의 예외 발생 검사범위를 지정하고 예외가 발생하면 throw절의 예외 데이터를 try문을 통해 catch로 전달한다.

 

catch문은 발생한 예외를처리하는 영역이며 반환형이 없는 함수처럼 정의되어있다.

 

try문과 catch문 사이에은 문장을 삽입할 수 없다.


스택풀기 -예외의 전달

#include<iostream>
using namespace std;

void Divide(int num1, int num2)
{
	if (num2 == 0)
		throw num2;				//예외 데이터 전달 [throw절을 감싸는 try 블록이 존재하지 않는 경우]
	

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

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

	try {
		Divide(num1, num2);
		//함수 호출
		cout << "나눗셈을 마쳤습니다." << endl;
	}
	catch (int expn)
	{
		cout << "제수는 " << expn << "이 될 수 없습니다." << endl;
		cout << "프로그램을 다시 실행하세요." << endl;
	}
	return 0;
}

 

함수 내 예외 데이터 num2를 처리할 수 없다. -> 함수 호출한 영역으로 데이터를 전달한다.
예외가 처리되지 않으면, 예외가 발생한 함수를 호출한 영역으로
예외 데이터와 예외를 처리해야 하는 책임이 전달된다.

 

즉 throw의 예외 데이터는 반드시 처리되어야 한다.


예외의 발생 위치와 처리위치가 달라야 하는 경우

#include <iostream>
#include <cstring>
#include <cmath>

using namespace std;

int StoI(char* str)
{
	int len = strlen(str);
	int num = 0;

	for (int i = 0; i < len; i++)
	{
		if (str[i] < '0' || str[i]> '9')
			throw str[i];

		num += (int)(pow((double)10, (len - 1) - i) * (str[i] + (7 - '7')));
	}
	return num;
}

int main(void)
{
	char str1[100];
	char str2[200];
	while (1)
	{
		cout << "두 개의 숫자 입력: ";
		cin >> str1 >> str2;

		try
		{
			cout << str1 << "+" << str2 << "=" << StoI(str1) + StoI(str2) << endl;
			break;
		}
		catch (char ch)
		{
			cout << "문자 " << ch << "가 입력되었습니다." << endl;
			cout << "재입력 진행합니다." << endl << endl;
		}
	}
	cout << "프로그램을 종료합니다." << endl;
	return 0;
}

 

 

	throw str[i];

예외의 데이터가 전달되면, 함수는 더이상 실행되지 않고 종료된다.

 

try
		{
			cout << str1 << "+" << str2 << "=" << StoI(str1) + StoI(str2) << endl;
			break;
		}
		catch (char ch)
		{
			cout << "문자 " << ch << "가 입력되었습니다." << endl;
			cout << "재입력 진행합니다." << endl << endl;
		}

함수의 비정상 종료처리는 main에서

예외의 처리는 main에서 처리


스택풀기

#include<iostream>
using namespace std;

void SimpleFuncOne(void);
void SimpleFuncTwo(void);
void SimpleFuncThree(void);

int main(void)
{
	try
	{
		SimpleFuncOne();

	}
	catch (int expn)
	{
		cout << "예외코드 : " << expn << endl;
	}
	return 0;
}
void SimpleFuncOne(void)
{
	cout << "SimpleFuncOne(void)" << endl;
	SimpleFuncTwo();
}
void SimpleFuncTwo(void)
{
	cout << "SimpleFuncTwo(void)" << endl;
	SimpleFuncThree();
}
void SimpleFuncThree(void)
{
	cout << "SimpleFuncThree(void)" << endl;
	throw - 1;
}

스택은 main -> SimpleFuncOne -> SimpleFuncTwo -> SimpleFuncThree 순으로 쌓이고

예외 데이터가 전달되면서 SimpleFuncThree -> SimpleFuncTwo -> SimpleFuncOne  -> main 순으로 스택이 해제된다.

 

main함수에서조차 예외를 처리하지 않으면 terminate함수가 호출되면서 프로그램이 종료된다.

 


자료형이 일치하지 않아도 예외 데이터는 전달


int SimpleFunc(void)
{
	try
	{
		if ()
			throw - 1; 	//int형 예외데이터 발생
	}
	catch (char expn)	//char형 예외데이터 전달
	{

	}
}

하나의 try 블록과 다수의 catch 블록

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;

int StoI(char* str)
{
	int len = strlen(str);
	int num = 0;
	if (len != 0 && str[0] == '0')
		throw 0;

	for (int i = 0; i < len; i++)
	{
		if (str[i] < '0' || str[i]> '9')
			throw str[i];

		num += (int)(pow((double)10, (len - 1) - i) * (str[i] + (7 - '7')));
	}
	return num;
}

int main(void)
{
	char str1[100];
	char str2[200];
	
	while (1)
	{
		cout << "두 개의 숫자 입력: ";
		cin >> str1 >> str2;

		try
		{
			cout << str1 << "+" << str2 << "=" << StoI(str1) + StoI(str2) << endl;
			break;
		}

		catch (char ch)
		{
			cout << "문자 " << ch << "가 입력되었습니다." << endl;
			cout << "재입력 진행합니다." << endl << endl;
		}

		catch (int expn)
		{
			if (expn == 0)
				cout << "0으로 시작하는 숫자는 입력불가." << endl;
			else
				cout << "비정상적 입력이 이루어졌습니다." << endl;
			cout << "재입력 진행합니다." << endl << endl;
		}
	}
	cout << "프로그램을 종료합니다." << endl;
	return 0;
}

하나의 try 영역 내에서 종류가 다른 둘 이상의 예외발생 가능

-> 하나의 try블록에 다수의 catch 블록 추가 가능

 


전달되는 예외의 명시

int ThorwFunc(int num) throw(int, char)
//예외 상황 발생시 int형 예외 데이터, char형 예외 데이터 발생가능 명시
{
...
}

int형, char형 외 예외 데이터 전달시, terminate 함수 호출로 프로그램 종료 [대비못한 예외상황의 처리]

 

try
{
ThrowFunc(20);
}
catch(int expn){}
catch(char expn){}

int형 예외데이터와 char형 예외데이터의 처리

 

어떤 예외가 와도 프로그램 종료

int SimpleFunc(void) throw()
{
...
}

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

 

#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 ShowExcpetionReason()
	{
		cout << "[예외메시지: 잔액 " << balance << ", 잔액부족]" << endl;
	}
};

class Account
{
private:
	char accNum[50];
	int balance;
public:
	Account(const char* acc, int money) :balance(money)
	{
		strcpy_s(accNum, strlen(acc) + 1, 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 ShowMyMoney()
		{
			cout << "잔고: " << balance << endl << endl;
		}
	
};

int main(void)
{
	Account myAcc("56789-827120", 5000);
	try
	{
		myAcc.Deposit(2000);
		myAcc.Deposit(-300);
	}
	catch (DepositException& expn)
	{
		expn.ShowExceptionReason();				//예외 객체의 멤버함수 호출
	}
	myAcc.ShowMyMoney();

	try {
		myAcc.Withdraw(3500);
		myAcc.Withdraw(4500);
	}
	catch (WithdrawException& expn)
	{
		expn.ShowExcpetionReason();				//예외 객체의 멤버함수 호출
	}
	myAcc.ShowMyMoney();
	return 0;
}

어떠한 유형의 에러인지 나타내기 위한 예외 클래스 [throw절에 오는 예외 객체를 생성한다.]

객체를 이용해 데이터와 행동을 나타낸다.

 

 

반응형