[따배C++] 14. 예외처리

Created: June 6, 2021 11:26 AM
Tag: exception handling, function try, rethrow, stack unwinding, std::exception

14.1 예외처리의 기본 exception handling


try, catch(던져진 예외를 받아서 처리한다), throw(예외를 던진다) 구문을 통해 예외처리를 할 수 있다. 이 방법은 문법적으로 깔끔하지만 퍼포먼스에 불리하기 때문에 상황에 따라 사용이 제한되어야 한다. 또한, 자료형에 따라 엄밀하게 작동(runtime error)하기 때문에 주의가 필요하다. ellipses 구문을 통해 이를 방지할 수 있다.

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(void)
{
	double x;
	cin >> x;

	try
	{
		if (x < 0.0) throw std::string("Negative input");

		cout << std::sqrt(x) << endl;
	}
	catch (std::string error_message) // <- 자료형에 주의
	{
		// do something to respond
		cout << error_message << endl;
	}

	return (0);
}

14.2 예외처리와 스택 되감기 stack unwinding


#include <iostream>
using namespace std;

void last() throw(int) // throw(int) <- exception specifier: int type의 예외를 던질 수도 있음을 명시!
// 불필요한것 아니냐는 견해
// 예외를 던질 가능성이 있는 함수
{
	cout << "last " << endl;
	cout << "throws exception" << endl;

	throw -1; // 다음부분 실행하지 않고 throw

	cout << "end last " << endl;
}

void third()
{
	cout << "third" << endl;

	last(); // 다음부분 실행하지 않고 throw

	cout << "end third" << endl;
}

void second()
{
	cout << "second" << endl;

	try
	{
		third();
	}
	catch (double) // throw는 int로 되었는데 double로 밖에 catch하지 못한다면 계속해서 날아간다
	{
		cerr << "second  caught double exception" << endl;
	}

	cout << "end second " << endl;
}

void first()
{
	cout << "first" << endl;

	try
	{
		second();
	}
	catch (int) // catch!
	{
		cerr << "first caught int exception" << endl;
	}

	cout << "end first" << endl;
}

int main(void)
{
	cout << "start" << endl;

	try
	{
		first();
	}
	catch (int)
	{
		cerr << "main caught int exception" << endl;
		// clog
	}

	// uncaught exceptions 못잡는 오류가 발생하는 경우
	catch (...) // catch-all handlers
	{
		cerr << "main caught ellipses exception" << endl;
	}

	cout << "end main" << endl;

	return (0);
}

14.3 예외 클래스(사용자 정의 자료형)와 상속 시 주의점


예외 클래스 상속 시 catch문에서 객체잘림과 유사한 현상이 발생할 수 있다. 이를 방지하는 방법과 rethrow에 대해 알아보자.

#include <iostream>
using namespace std;

class Exception
{
public:
	void report()
	{
		cerr << "Exception report" << endl;
	}
};

class ArrayException : public Exception
{
public:
	void report()
	{
		cerr << "Array exception" << endl;
	}
};

class MyArray
{
private:
	int m_data[5];

public:
	int &operator [] (const int &index)
	{
		if (index < 0 || index >= 5) throw ArrayException();

		return (m_data[index]);
	}
};

void doSomething()
{
	MyArray my_array;

	try
	{
		my_array[100];
	}
	catch (const int &x)
	{
		cerr << "exception" << x << endl;
	}
	catch (ArrayException &e)
	{
		e.report();
		throw e; // rethrow
		// throw; // 객체 잘림 발생하지 않음
	}
	catch (Exception &e)
	// catch (Exception &e)이 먼저 작성되어 있다면
	// 받는 쪽에서 ArrayException이 아닌 Exception으로 받기 때문에
	// 객체잘림과 유사하게 Exception report 출력
	{
		e.report();
	}
}

int main(void)
{
	try
	{
		doSomething();
	}
	catch(ArrayException &e)
	{
		cout << "main()" << endl; // rethrow 한것을 받는다
		e.report();
	}

	return (0);
}

14.4 std::exception 소개


다양한 경우에 대한 예외처리가 std::exception을 통해 이미 구현되어 있다. 이를 활용하는 방법에 대해 알아보자.

#include <iostream>
#include <exception> // <-
#include <string>

class CustomException : public std::exception
{
public:
	const char *what() const noexcept override
	{
		return ("Custom exception");
	}
};

int main(void)
{
	try
	{
		throw std::runtime_error("Bad thing happend");
	}
	catch (std::length_error &exception)
	{
		std::cerr << exception.what() << std::endl;
	}
	catch (std::exception &exception)
	{
		std::cerr << exception.what() << std::endl;
	}

	return (0);
}

14.5 함수 try


#include <iostream>
using namespace std;

class A
{
private:
	int m_x;
public:
	A(int x) : m_x(x)
	{
		if (x <= 0)
			throw 1;
	}
	B(int x) try : A(x)
	{
		// do init
	}
	catch (...)
	{
		cout << "catch in B constructor " << endl;
		// throw; // 없어도 있는것처럼 작동!
	}
};

class B : public A
{
public:
	B (int x)
		: A(x) // init
	{
	}
};

void doSomething()
{
	try
	{
		throw - 1;
	}
	catch (...)
	{
		cout << "catch in doSomething()" << endl;
	}
}

int main(void)
{
	try
	{
		// doSomething()
		B b(0);
	}
	catch (...)
	{
		cout << "catch in main(void)" << endl;
	}

	return (0);
}

14.6 예외처리의 위험성과 단점


  • 소멸자 안에서 throw는 금기시 된다. → runtime error!
  • 스마트 포인터를 사용하게 되면 영역을 벗어나게 되었을때(throw) 자동으로 메모리를 해제한다.
#include <iostream>
#include <memory>
using namespace std;

class A
{
public:
	~A()
	{
		throw "error"; // <-
	}
};

int main(void)
{
	try
	{
		int *i = new int[10000000];
		unique_ptr<int> up_i(i); // <-

		// do something with i
		throw "error";

		// delete[] i;

		// A a;
	}
	catch (...)
	{
		cout << "catch" << endl;
	}

	return (0);
}

좋은 웹페이지 즐겨찾기