C++ Exception Handling

이정빈·2023년 6월 1일
0

1. 기본 문법

1.1 Try, Catch, Throw

Throw문은 항상 try블록 안에 위치해야함 (종료)
예외처리할 catch문 없으면 (종료)
catch 블록 내에서도 try catch문 가능

Try 블럭은 예외를 만들어낼 수 있는 블럭

catch 블럭은 예외를 다루는 블럭

Throw 블럭은 catch에다가 예외를 던짐

void main() {
	int n1 = 5;
	int n2 = 0;
	try {
		if (n2 == 0) {
			throw n2;
		}
		else {
			cout << n1 / n2 << endl;
		}
	}
	catch (char) {
		cout << "error" << endl;
	}
	return;
}

n2가 0이므로 throw에서 catch로 n2를 던짐

하지만 int 타입을 잡아내는 catch문이 없어서 오류가 생김

catch(...){~~~~}

위의 문장은 모든 에러들을 잡아냄

void main() {
	int n;
	try {
		cin >> n;
		switch (n) {
		case 1:
			throw 1;
			break;
		case 2:
			throw 'a';
			break;
		default:
			throw 1.5;
			break;
		}
	}
	catch (int) {
		cout << "error: int" << endl;
	}
	catch (char) {
		cout << "error: char" << endl;
	}
	catch (...) {
		cout << "error";
	}
}
void main() {
	try {
		try {
			throw 1.5;
			
		}
		catch (int e) {
			cout << "inner " << e << endl;
		}
		catch (...) {
			cout << "inner rethrow" << endl;
			throw;
		}
	}
	catch (double e) {
		cout << "outer " << e;
	}
}

위와 같이 catch문이 여러 개 있어도 되고, try안의 try문이 있어도 된다.

1.2 set_terminate()

void myHandler() {
	cout << "my hand" << endl;
	exit(EXIT_FAILURE);
}

void main() {
	int n1 = 5;
	int n2 = 0;
	set_terminate(myHandler);

	try {
		if (n2 == 0) {
			throw n2;
		}
	}
	catch (char) {
		cout << "error" << endl;
	}
	return;
}

my hand

적당한 예외 처리기를 찾지 못할 때, 사용된다.
throw에서 n2를 던지면 catch문에서 int형을 찾을 것이다.
그런데 int형이 없다. 그러면 바로 set_terminate에서 지정한 MyHandler()함수에 간다.

set_terminate, catch(...) 서로 최후의 수단인걸로 비슷하다.

set_terminate

이 함수는 프로그램 내에서 예외가 처리되지 않고 예외 처리기인 catch문에 잡히지 않을 때 호출된다. 또는 throw문이 사용되지 않은 경우에도 호출된다. 이 함수로 예외 처리를 마지막으로 시도하고 그렇게 해도 처리가 되지 않으면 종료한다.

catch(...)
모든 종류의 예외를 잡을 수 있으며, 이전의 catch블록에서 못잡은 예외를 잡으려는 용도이다.

1.3 Stack Unwinding

void func3() {
	cout << "f3" << endl;
	throw 1;
	cout << "f3 exit" << endl;
}

void func2() {
	cout << "f2 " << endl;
	func3();
	cout << "f2 exit" << endl;
}

void func1() {
	cout << "func1" << endl;
	try {
		func2();
	}
	catch (int x) {
		cout << "catch " << x << endl;
	}
	cout << "f1 exit" << endl;
}

void main() {
	func1();
}

func1
f2
f3
catch 1
f1 exit

throw와 catch 사이의 stack을 모두 없애버림
메모리 누수가 생기지 않도록 조심해야함

1.4 생성자 예외 처리

class myClass {
private:
	string data;
public:
	myClass(const string& m) :data(m) {
		cout << "constructor : " << data << endl;
	}
	myClass(const myClass& r) :data(r.data + "copy") {
		cout << "Copy constructor : " << data << endl;
	}
	string print() const {
		return data;
	}
	~myClass() {
		cout << "destructor: " << data << endl;
	}
};

void main() {
	try {
		myClass my{ "data" };
		throw my;
	}
	catch (myClass e) {
		cout << e.print() << endl;
	}
}

constructor : data
Copy constructor : data copy
Copy constructor : data copy copy
destructor: data
data copy copy
destructor: data copy copy
destructor: data copy

  1. 파라미터 생성자 호출
  2. throw할 때 복사 생성자 호출
  3. catch할 때 복사 생성자 한 번 더 호출
  4. 범위가 끝났으므로 파라미터 생성자는 소멸
  5. e.print()
  6. catch문이 끝났으므로 catch문에서 생성된 생성자는 소멸
  7. throw도 끝났으니 소멸자 호출
    throw가 제일 마지막에 사라짐
class myClass {
private:
	string data;
public:
	myClass(const string& m) :data(m) {
		cout << "constructor : " << data << endl;
	}
	myClass(const myClass& r) :data(r.data + " copy") {
		cout << "Copy constructor : " << data << endl;
	}
	string print() const {
		return data;
	}
	~myClass() {
		cout << "destructor: " << data << endl;
	}
};

void main() {
	try {
		myClass my{ "data" };
		throw my;
	}
	catch (const myClass& e) {
		cout << e.print() << endl;
	}
	cout << "run";
}

constructor : data
Copy constructor : data copy
destructor: data
data copy
destructor: data copy
run

catch할 때 참조를 하므로 복사 생성자가 만들어지지 않는다.

1.5 Standard Exception Class

void main() {
	try {
		throw out_of_range{ "error" };
	}
	catch (const out_of_range& e) {
		cout << "1" << e.what() << endl;
	}
	catch (const logic_error& e) {
		cout << "2" << e.what() << endl;
	}
	catch (const exception& e) {
		cout << "3" << e.what() << endl;
	}

}
class Div {
private:
	string data;
public:
	Div(const string& m):data(m){}
	string print() const {
		return data;
	}
};

double quo(int x, int y) {
	if (y == 0) {
		throw Div{ "zero" };
	}
	return static_cast<double>(x) / y;
}

void main() {
	int n1 = 5;
	int n2 = 0;
	try {
		cout << quo(n1, n2) << endl;
	}
	catch (const Div& e) {
		cout << e.print() << endl;
	}
}

zero

e.print()하고 throw가 사라짐. 생각을 해보면 참조이기때문에 먼저 사라지면 안됨
맞춤된 클래스는 지나서 catch문으로 간다.

public:
	Div() :runtime_error{ "zero" } {}
};

double quo(int x, int y) {
	if (y == 0) {
		throw Div();
	}
	return static_cast<double>(x) / y;
}

void main() {
	int n1 = 5;
	int n2 = 0;
	try {
		cout << quo(n1, n2) << endl;
	}
	catch (const Div& e) {
		cout << e.what() << endl;
	}
}

runtume_error를 상속받아서 사용 가능하다. 가장 흔한 접근 법이다.
throw문에서 Div 클래스를 생성한다.
그리고 이 생성자를 catch문에 전달하고 catch문에서 출력한다.

열혈 예외처리 30페이지에서 상속 받은 클래스들이 있고 main에서 함수를 호출해 의도적으로 오류를 처리시킨다. 하지만 상속처리되어 있으면 무조건 base클래스부터 생성자를 호출하기 때문에 옳지 않은 결과가 나온다. 이럴때는 역순을 취해준다.

Prac 1

void Checker() throw(char*) {
	int ch;
	int cnt = 0;
	while ((ch = cin.get()) != EOF) {
		if (ch == '@') {
			cnt++;
		}
		if (ch == '\n') {
			break;
		}
	}
	if (cnt < 1) {
		throw "Invalid Email Id!!!";
	}
}

int main() {
	try {
		cout << "Enter Email Id : ";
		Checker();
	}
	catch (const char* s) {
		cout << "\nException Caught...\n";
		cout << s;
		return 0;
	}
	cout << "\nEmail Id is Valid\n";
	return 0;
}

Enter Email Id : ljb011013@naver.com

Email Id is Valid

Enter Email Id : ljb011013.naver.cm

Exception Caught...
Invalid Email Id!!!

0개의 댓글