본문 바로가기
2CHAECHAE 학교생활/객체지향프로그래밍(C++)

[ 객체지향프로그래밍(C++) 13주차 ① ]

by 2CHAE._.EUN 2022. 6. 4.

[ 13주차 - 1 ] 

 

예외 처리

 

예외처리는 프로그램을 실행할 때 에러가 발생하면 그 에러에 대해서 내 컴퓨터에서 직접 처리해서 정상적으로 종료해주는 것이다.

 

#include <iostream>
using namespace std

try {

	// 에러 부분에 대해 감시하고자 하는 프로그램 부분을 포함
	
}
catch( type1 arg ) {

	//예외 처리
	//예외의 형이 어느 catch문을 사용할 것인가를 결정

}
cathch(type2 arg) {


}

 

try 부분은 프로그램 실행 부분이다. 이 부분에서 예외( 에러 )가 발생하면 발생하는 에러의 종류에 따라 여러 곳에서 catch가 가능하다. 예외가 어느 type이냐에 따라 1개의 예외 처리 부분에 catch가 된다. 에러가 발생하더라도 프로그램이 catch를 해서 에러 처리를 하기 때문에 정상적인 종료가 될 수 있다.

 

예외의 형이 어느 catch문을 사용할 것인가를 결정한다. → 예외의 형이 catch문에 명시된 자료형과 일치해야만 한다.

프로그램에서 throw문을 이용하여 예외를 발생시킬 수 있다. 

throw exception;

throw는 exception에 의해 명시된 예외를 발생시킨다.

 

적용될 catch문이 없는 예외가 던져진다면 비정상적인 프로그램 종료가 발생할 수 있다.

 

try는 예외 처리될 실행문들의 범위를 정해준다. 

 

#include <iostream>
using namespace std;

int main() {

	cout << "start!\n";
	try {
		cout << "inside try block\n";
		throw 99; //에러를 던진다.
		//예외가 던져지면 catch문으로 제어가 넘어가고 try 블럭은 종료된다.
		cout << "This will not execute";
	}
	catch (int i) {
		cout << "Caught an exception : ";
		cout << i << "\n";
	}

	cout << "end";
	return 0;
}

// start!
// inside try block
// Caught an exception : 99
// end

 

throw 99에서 99의 자료형은 int이기 때문에 catch중에 int에 대한 부분에 제어가 넘어가게 된다. 99는 i에 복사가 된다.

 

예외가 발생되면 바로 catch문을 실행하기 때문에 try 블럭 내에서 예외가 발생한 이후의 코드 부분은 실행되지 않는다.

 

클래스형 예외 처리

 

자바나 C++에서 예외 처리를 할 때는 보통 클래스의 객체를 던진다.

예외를 위한 클래스를 정의하는 가장 공통된 이유는 발생했던 에러를 설명하는 객체를 생성하기 위한 것이다.

→ 설명을 클래스 안에다가 저장해놨다가 실제로 예외가 발생한다면 그 클래스 객체에 저장되어있는 설명을 출력해서 무슨 에러가 발생했는지 파악할 수 있다.

 

#include <iostream>
using namespace std;

class MyException { // 예외에 대한 설명을 저장
public:
	char str_what[80];

	MyException() {
		*str_what = 0;
	}
	MyException(const char* s) { // 문자열을 str_what에 넣어준다.
		strcpy_s(str_what, s);
	}
};

int main() {

	int a, b;
	try {
		cout << "Enter numerator and denominator : ";
		cin >> a >> b;
		if (!b) {
			throw MyException("Cannot divide by Zero");
		}
		else {
			cout << "Quotient is " << a / b << "\n";
		}
	}
	catch (MyException e) { // 에러를 잡는다.
		cout << e.str_what << "\n";
	}

	return 0;

}

// Enter numerator and denominator : 7 0
// Cannot divide by Zero

 

b가 0이라면 MyException 객체가 생성이 되어서 던져진다. 생성자 함수에 의해서 Cannot divide by Zero 문자열이 str_what 문자열 변수에 들어가게 된다. 예외가 던져졌기 때문에 MyException 클래스 형과 일치하는 catch에서 에러를 잡고 catch문 안에 들어있는 코드를 실행한다. 매개변수 e에는 MyException 객체를 그대로 복사해서 받게 된다.

 

MyException 객체 안에는 str_what이라는 문자열 변수가 들어있고 출력해보면 클래스 객체 내에 저장되어있는 설명을 확인할 수 있다.

 

오류가 발생하지 않는다면 catch문이 발생하지 않고 정상적으로 프로그램이 종료된다. 즉, 특정한 에러를 방지하기 위해 예외 처리를 사용한다.

 

 

다중 catch문 사용

 

catch식은 프로그램에 있는 순서대로 확인이 된다.  단 하나의 일치하는 문장만이 실행된다. 모두 다른 catch 블록은 무시된다.

 

#include <iostream>
using namespace std;

// 다른 형의 예외를 잡을 수 있다.
void Xhandler(int test) {
	try {
		if (test) { //0이 아니면 무조건 throw test를 실행해 값을 전달
			throw test;
		}
		else { // 0이라면 문자열이 던져진다.
			throw "Value is zero";
		}
	}
	catch (int i) { //정수에 대한 예외처리
		cout << "Caught one! : " << i << "\n";
	}
	catch (char* str) { //문자열에 대한 예외처리
		cout << "Caught a string : ";
		cout << str << "\n";
	}
}

int main() {

	cout << "start\n";

	Xhandler(1);
	Xhandler(2);
	Xhandler(0);
	Xhandler(3);
	cout << "end\n";

	return 0;
}

 

 

부모 클래스의 예외 잡기

 

부모 클래스를 위한 catch문은 그 부모 클래스로 부터 파생된 어떤 클래스와도 일치한다.

부모 클래스 형과 자식 클래스 형 모두의 예외를 잡고 싶다면, 여러 catch문들 중 첫번째에 자식 클래스를 둔다.

그렇지 않다면 부모 클래스 catch가 모든 자식 클래스들을 잡을 것이다.

 

즉, 제일 밑 레벨의 클래스를 먼저 catch 하도록 catch 순서를 정함으로써 각각의 맞는 클래스 객체를 잡을 수 있다.

 

#include <iostream>
using namespace std;

class B {

};

class D : public B {

};

int main() {

	D derived; 

	try {
		throw derived;
	}
	catch(B b) {
		cout << "Caught a parent class\n";
	}
	catch (D d) {
		cout << "This won't execute\n";
	} 
	 
	return 0; 
}

 

catch문의 순서가 부모 클래스 → 자식 클래스이므로 부모클래스가 먼저 catch가 되므로 try문에서 예외를 던지면 제일 첫번째 catch에서 잡힌다. 그래서 자식 클래스에 해당하는 에러 처리에 대한 부분은 실행되지 않는다.

즉, 부모 클래스가 순서 관계에서 먼저 정의되어있기 때문에 어떤 객체를 던지더라도 부모 클래스에 해당하는 예외 처리 부분에서 다 잡힌다. 

 

catch문의 클래스 순서를 반대로 해서 자식 클래스 → 부모 클래스로 정의한다면 자식 클래스 객체를 던질 경우 자식 클래스에 대한 catch에서 잡히고 부모 클래스 객체를 던질 경우 부모 클래스에 대한 catch에서 잡히게 된다.

 

 

예외 처리를 위한 옵션

 

모든 예외를 잡음으로써 처리되지 않은 예외가 비정상적으로 프로그램을 종료시키는 것을 방지한다.

 

...은 어떠한 형의 데이터가 와도 일치한다.

 

#include <iostream>
using namespace std;

// 모든 예외를 잡는다.
void Xhandler(int test) {
	try {
		if (test == 0) {
			throw test; // int를 던진다.
		}
		if (test == 1) {
			throw 'a'; // char를 던진다.
		}
		if (test == 2) {
			throw 123.12; // double를 던진다.
		}
		// test 값이 0,1,2가 아니라면 그냥 프로그램이 끝나게 된다.
	}
	catch (int i) { //정수에 대한 예외처리
		cout << "Caught one! : " << i << "\n";
	}
	catch (...) { // ...은 어떠한 형의 데이터가 와도 일치한다.
		cout << "Caught all\n";
	}
}

int main() {

	cout << "start\n";

	Xhandler(0);
	Xhandler(1);
	Xhandler(2);

	cout << "end\n";

	return 0;

}

// start
// Caught one! : 0
// Caught all
// Caught all
// end

 

 

함수에 의해 던져진 예외를 제한하기

 

함수가 자신 밖으로 던질 수 있는 예외의 형을 제한할 수 있다.

 

ret-type func-name(arg-list) throw(type-list){

	// ... 

}

// 이 함수는 오직 throw 만 하고 메인함수의 try,catch 안에서 이 함수를 호출한다.
// 이 함수가 항상 throw를 실행하는 것은 아니다. 
// 예외가 발생하면 throw를 하게 되는데 함수에서 throw를 catch 하는 것이 아니라 호출한 곳에서
// 그 throw에 대해 catch 한다.

 

함수를 호출하는 부분에서 예외가 발생한다면 예외가 발생한 함수의 try/catch에서 catch를 한다. 함수를 정의할 때, 함수가 던질 수 있는 예외의 형을 제한할 수 있다. 예외의 형을 제한하는 방법은 함수를 선언하는 부분에서 뒤에 바로 throw를 작성해주고 매개변수로 throw할 수 있는 자료형들을 명시해주면 된다. 자료형이 여러 개일 경우는 콤마를 사용해서 분리하면 된다.  즉, 콤마로 분리된 type-list에 포함된 자료형만이 이 함수에 의해 던져질 수 있다. 

 

다른 형을 던지는 것은 비정상적인 프로그램 종료를 하게 한다.

어떠한 예외도 던질 수 없게 하고 싶다면 빈 리스트를 사용한다.

 

#include <iostream>
using namespace std;

// 함수가 던지는 예외 형을 제한한다.
// 함수는 단지 int, char, double 만을 던질 수 있다.
void Xhandler(int test) throw(int, char, double){
	if (test == 0) {
		throw test; // int를 던진다.
	}
	if (test == 1) {
		throw 'a'; // char를 던진다.
	}
	if (test == 2) {
		throw 123.12; // double를 던진다.
	}
}

int main() {

	cout << "start\n";
	try{
		Xhandler(0);
	}
	catch (int i){
		cout << "Caught int\n";
	}
	catch (char c) {
		cout << "Caught char\n";
	}
	catch (double d) {
		cout << "Caught double\n";
	}

	cout << "end";
	return 0;
}

// start
// Caught int
// end

 

Xhandler는 test 값이 0,1,2에 따라서 int, char, double을 던지기만 한다. 그리고 메인함수에서 try/catch를 한다. 

정수형, 문자형, double형 이외에 다른 형의 예외를 던지려고 한다면, 비정상적인 프로그램 종료가 일어나게 된다.

 

 

실습문제

 

double divide( double a, double b ){

	//에러처리
    return a/b;
    
}

 

divide 함수는 a를 b로 나누는 결과를 반환한다. 예외처리를 사용해서 에러 검사 기능을 추가한다.

0으로 나누는 에러를 방지하고 메인 함수를 작성하여 프로그램이 작동하는가를 보여라.