C++ 예외 발생 후 고려해야할 문제

프로그래밍/C++ 2019.11.02 댓글 Plorence

예외가 발생한 후에도 문제를 일으킬 수 있는 두 가지 가능성이 있습니다.

 

예외가 발생했을 때 예외 지정자 리스트에 있는 데이터형들과 일치하는 게 없을 때

일치하는 게 없다면 그 예외는 기대하지 않는 예외(unexpected exception)라는 낙인이 찍히고 기본적으로 프로그램 실행이 중지됩니다.

 

예외가 발생한 후에 try블록이 없거나 예와 일치하는 catch블록이 없을 때(예외가 포착되지 않았을 때)

이러한 경우에는 포착되지 않는 예외(ucaught exception)라는 낙인이 찍히고 기본적으로 프로그램 실행이 중지됩니다.

 

이 둘은 예외에 대한 프로그램 응답을 사용자가 변경할 수 있습니다.

 

포착되지 않는 예외

예외가 발생했으나 포착되지 않는 예외가 발생했을 때 다음과 같은 순서로 진행합니다.

  1. terminate함수를 호출함

  2. terminate함수는 abort함수를 호출함(기본)

  3. abort함수로 인하여 프로그램이 중지됨

 

여기서 abort함수 대신 다른 함수를 호출할 수 있도록 등록하면 됩니다.

등록하기 위해서는 set_terminate함수를 호출하여야 하는데 exception 헤더 파일에 선언되어 있습니다.

(terminate함수도 마찬가지)

 

set_terminate함수를 호출하여 등록할 때 매개변수는 함수의 주소를 매개변수로 사용합니다.

이때 함수는 매개변수를 사용하지 않고 리턴형이 void여야 합니다.

그리고 set_terminate함수를 호출했을 때 이전에 등록된 함수의 주소를 리턴합니다.

terminate함수를 호출하면 가장 최근에 set_terminate함수를 통해 등록한 함수가 호출됩니다. 

#include <iostream>
#include <exception>
int DiviAdd(int a, int b) throw(int) { // a/b의 결과값을 각각 a,b에 더하는 함수
	if (a == 0 || b == 0) {
		throw "Exception";
	}
	int result = a / b;
	return (a + result) + (b + result);
}
void AppExit() {
	std::cout << "기대하지 않는 예외가 발생했습니다. 프로그램을 종료하겠습니다.";
	exit(1);
}
int main(void) {
	void * func = std::set_terminate(AppExit);
	try {
		DiviAdd(1, 0);
	}
	catch (int) {
		std::cout << "Int Exception!";
	}
	return 0;
}
기대하지 않는 예외가 발생했습니다. 프로그램을 종료하겠습니다.

 

기대하지 않는 예외

C++ 11 이후로 무시되기 때문에 설명 X

간단하게 설명하면 terminate함수 앞에 unexpected함수를 호출합니다. 그리고 abort() 함수가 호출됩니다. (unexpected() > termniate() > abort())

마찬가지로 등록하는 함수 set_unexpected함수가 있습니다.

#include <iostream>
#include <exception>
int DiviAdd(int a, int b) throw(int) { // a/b의 결과값을 각각 a,b에 더하는 함수
       if (a == 0 || b == 0) {
              throw "Exception";
       }
       int result = a / b;
       return (a + result) + (b + result);
}
void AppExit() {
       std::cout << "기대하지 않는 예외가 발생했습니다. 프로그램을 종료하겠습니다.";
       exit(1);
}
int main(void) {
       std::set_unexpected(AppExit);
       try {
              DiviAdd(1, 0);
       }
       catch (int) {
              std::cout << "Int Exception!";
       }
       return 0;
}

 

예외 주의사항

예외를 사용하면 프로그램의 크기가 커지고 실행 속도도 떨어집니다.

예외는 템플릿과는 안 어울리는데 그 이유는 템플릿 함수들은 사용하는 특수화에 따라 서로 다른 종류의 예외를 발생시킬 수 있기 때문입니다.

그리고 throw는 변수는 정리되지만 지시하고 있는 메모리는 여전히 할당되어 있습니다.

void test() {
       char * a = new char[10];
       //...
       if (strlen(a) == 1) {
              throw std::exception();
       }
       delete[] a;
       return;
}

만약 throw를 통해 예외가 발생된다면 delete [] a구문을 실행하지 않습니다.

그러므로 할당한 메모리는 메모리상에서 그대로 있고 누수가 발생하게 됩니다.

방법은 두 가지가 있는데 throw 하기 전에 바로 할당을 해제해주거나

try블록으로 감싸서 예외 발생 시 할당 해제하는 방법이 있습니다.

#include <iostream>
void test() {
       char * a = new char[10];
       //...
       /*첫 번째 방법!!
       if (strlen(a) == 1) {
              delete[] a;
              throw std::exception();
       }
       */
       /*두 번째 방법!!
       try {
              if (strlen(a) == 1) {
                     throw std::exception();
              }
       }
       catch (std::exception & exce) {
              delete[] a;
              throw;
       }
       */
       delete[] a;
       return;
}
int main(void) {
       test();
}

첫 번째 방법이나 두 번째 방법이나 깔끔하지 못한 방법입니다.

이때는 스마트 포인터를 사용하는 게 좋습니다.

#include <iostream>
void test() {
	std::unique_ptr<char> a = std::make_unique<char>(10);
	char* str = a.get();
	for (int i = 0; i <= 8; i++) {
		str[i] = 'a';
	}
	str[9] = 0;
	if (strlen(a.get()) == 1) {
		   throw std::exception(); //Throw시 자동으로 a는 소멸
	}
	return;
}
int main(void) {
	test();
}

댓글