본문 바로가기

Information Technology/C++

[C++] 예외 클래스와 상속

이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요 ;)


지금까지 포스팅을 통해 다룬 예외처리는 int, float, char과 같이 일반적인 자료형 변수를 대상으로 했습니다.

이번 포스팅에서는 사용자 정의 자료형인 클래스Class를 통해 예외처리를 하는 방법을 알아보겠습니다.

class MyArray
{
private:
	int m_data[5];
public:
	int& operator[] (const int& index)
	{
	  	if (index < 0 || index >= 5) throw - 1;

		return m_data[index];
	}
};

여기 간단하게 클래스 하나를 정의했습니다. 크기가 5인 int형 배열 m_data를 멤버로 가지고,

연산자 오버 로딩을 통해 첨자([ ])를 오버 로딩했습니다. 그리고 첨자 오버 로딩 내부에 예외처리를 구현했습니다.

m_data의 크기는 5로 정해져 있으니, 첨자를 통해 배열의 값을 바꿀 때 index가 5보다 크거나 0보다 작으면 예외처리로 -1을 throw 합니다.

즉, 클래스의 멤버 함수에서도 예외처리를 구현할 수 있습니다.

void doSomething()
{
	MyArray my_array;

	try
	{
		my_array[100];
	}
	catch (const int &x)
	{
		cerr << "Exception " << x << endl;
	}
}

그리고 doSomething이라는 함수에서 MyArray 클래스의 인스턴스인 my_array를 초기화하고,

try구문에서 첨자를 통해 100의 인덱스 값을 주게 되면, MyArray 클래스에서 예외처리를 구현한 대로 -1을 throw 하여 catch구문에서 "Exception -1"을 출력하게 됩니다.


이번에는 클래스, 그러니까 사용자 정의 자료형을 예외처리에 사용하는 방법에 대해 설명하겠습니다.

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

여기 이름부터 예외처리에 사용될 것 같은 클래스가 있습니다. 멤버로는 report 함수만 가지고 "Exception report"라는 문구를 출력합니다.

class MyArray
{
private:
	int m_data[5];

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

그리고 이번엔 MyArray 클래스에서 예외처리 코드를, -1을 throw하지 않고 대신 위에서 정의한 Exception클래스의 default값을 throw 하도록 만들었습니다.

void doSomething()
{
	MyArray my_array;

	try
	{
		my_array[100];
	}
	catch (Exception & e)
	{
		e.report();
	}
}

그리고 doSomething 함수에서 catch 구문이 Exception클래스 자료형 인스턴스를 받도록 바꾸면, 위의 try 구문에서 throw된 값을 catch 구문이 받아 Exception 클래스의 멤버인 report함수를 출력합니다.

클래스 역시 일반 자료형과 마찬가지로 throw하는 값과 catch 하는 값을 설정해주면 예외처리에 사용할 수 있습니다.


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

이번에는 위에서 구현한 Exception 클래스를 상속받는 ArrayException 클래스를 만들었습니다. 멤버는 동일하게 report 함수를 갖고, "Array exception"이라는 에러 문구를 출력합니다.

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

그리고 이번에는 MyArray 클래스에서 예외처리로 ArrayException의 default 인스턴스를 호출하게 만들고 doSomething함수를 실행합니다.

void doSomething()
{
	MyArray my_array;

	try
	{
		my_array[100];
	}
    	catch (Exception & e)
	{
		e.report();
	}
	catch (ArrayException & e)
	{
		e.report();
	}
}

 

 

MyArray 클래스의 멤버함수가 예외처리가 발생하면 ArrayException 클래스를 throw 하도록 설정했으니 위의 코드에서도 ArrayException을 catch 해서 호출되는 "Array exception"이 출력될까요?

정답은 No 입니다.

위의 코드에서는 ArrayException을 받는 catch 구문보다 Exception을 받는 catch 구문이 먼저 선언되었습니다. ArrayException 클래스는 Exception 클래스를 상속하기 때문에, 위의 코드 블록처럼 부모 클래스를 받는 catch구문이 먼저 선언되어있으면 자식 클래스가 throw 되어도 부모 클래스의 catch구문에서 이를 처리해버립니다.

때문에, 부모 클래스와 자식 클래스를 동시에 예외처리로 사용하려면, 자식 클래스를 받는 catch구문을 먼저 선언해야 합니다.


int main()
{
	try
	{
		doSomething();
	}
	catch (ArrayException & e)
	{
		cout << "main()" << endl;
		e.report();
	}

	return 0;
}

이번에는 main 함수로 넘어왔습니다.

이전 포스팅에서 다뤘던 것처럼, main 함수 안에서 try 구문과 catch 구문을 실행하고 있습니다.

그런데 try구문에서 실행한 doSomething 함수는 함수 자체에서 throw와 catch를 모두 처리합니다. 즉, 스택 되감기(stack unwinding)이 발생하면서 main 함수 안에 있는 catch 구문을 실행되지 않습니다.

만약 프로그래머가 doSomething 함수에서 예외처리를 모두 끝낸 후에도 main함수의 catch 구문을 사용해야 하는 상황이라면 어떻게 해야 할까요?

catch (ArrayException & e)
	{
		cout << "doSomething()" << endl;
		e.report();
		throw;
	}

그때는 doSomething 안에 정의된 catch구문을 정의할 때, 마지막 부분에 throw를 다시 선언하면 됩니다.

이를 re-throw라고 부릅니다. 이렇게 throw를 다시 선언하게 되면 catch 구문이 throw를 통해 받은 값을 그대로 다음 catch 구문에게 전달하게 됩니다.

catch (ArrayException & e)
	{
		cout << "doSomething()" << endl;
		e.report();
		throw e;
        	// throw -1;
        	// throw 'a';
	}

또한 throw를 선언할 때 -1, 'a' 등등 사용하려는 자료형을 정해서 선언하게 되면 여기서 사용된 자료형을 받는 catch 구문에게 전달됩니다!