22. 예외 처리(Exception Handling)
1. 키워드
- 예외 처리(Exception Handling)
try
,thorw
,catch
문- 스택 풀기(Stack Unwinding)
exception
클래스stdexcept
헤더 파일logic_error
와runtime_error
클래스terminate()
와set_terminate()
함수
2. try
, throw
, catch
문
1) 예외란?
- 예외란 컴퓨터 시스템이 동작하는 도중에 예상하지 못한 오류가 발생하여, 실행되고 있던 프로그램이 중지되는 것을 의미한다.
- 예외 처리는 이러한 예외 상황을 처리할 수 있도록 코드의 흐름을 바꾸는 행위를 의미한다.
- C++은 언어 차원에서 예외 처리 문법을 제공하여, 예외 처리하는 방식을 확장하고 관리하기 쉽도록 해준다.
2) try
, throw
, catch
문
- C++에서는 예외 처리의 구현을 위해서
try
,throw
,catch
문을 제공한다.
1] try
- 예외가 발생할 가능성이 있는 코드 블록
2] throw
try
문에서 발생한 오류에 대한 정보를 전달
3] catch
- 발생한 예외에 대해 예외 핸들러가 처리할 내용을 담은 코드 블록
try
문으로 예외를 처리하기 위해서는try
문 다음에 반드시 하나 이상의catch
절을 구현해야 한다.- 또한, 각
catch
절은 자신이 처리할 수 있는 예외 타입을 지정할 수 있다. - 이때 특정 예외 타입 대신에
...
(줄임표)를 사용하면, 해당catch
절은 모든 타입의 예외를 처리하게 된다. - 하지만 이러한
...
를 사용한catch
절의 위치는 언제나catch
절 중 맨 마지막에 위치해야 한다.
- 다음의 코드는 사용자가 정확히 양의 정수를 입력했는가를
throw
문으로 검사하는 것이다.
#include <iostream>
using namespace std;
int IncreaseNumber(int n) {
if (n < 0) {
throw n;
} else if (n == 0) {
throw "0은 입력할 수 없습니다.";
}
return ++n;
}
int main(void) {
int num;
int inc_num;
cout << "양의 정수를 하나 입력해 주세요: ";
while (cin >> num) {
try {
inc_num = IncreaseNumber(num);
cout << "입력한 정수에서 1을 증가시킨 값: " << inc_num << endl;
} catch (int input) {
cout << input << "은 양의 정수가 아닙니다." << endl;
cout << "양의 정수를 다시 입력해 주세요: ";
continue;
} catch (const char *st) {
cout << st << endl;
cout << "양의 정수를 다시 입력해 주세요: ";
continue;
}
break;
}
return 0;
}
// 양의 정수를 하나 입력해 주세요: -3
// -3은 양의 정수가 아닙니다.
// 양의 정수를 다시 입력해 주세요: 7
// 입력한 정수에서 1을 증가시킨 값: 8
- 위의 코드처럼
throw
문의 피연산자는 변수와 문자열을 포함한 모든 타입의 예외를 사용할 수 있다. - 하지만 C++ 표준에서는
throw
문의 피연산자로std::exception
에서 파생되는 예외 타입을 사용하도록 권장하고 있다.
3) 예외 메커니즘
- C++에서 예외 처리는 다음과 같은 순서로 진행된다.
1] try
문에 도달한 프로그램의 제어는 try
문 내의 코드를 실행한다.
2] 이때 예외가 발생(throw
)하지 않으면 프로그램의 제어는 맨 마지막 catch
절 바로 다음으로 이동한다.
3] 만약 예외가 발생하면 catch
핸들러는 다음과 같은 순서로 적절한 catch
절을 찾게 된다.
3-1] 스택에서 try
문과 가장 가까운 catch
절부터 차례대로 검사한다.
3-2] 만약 적절한 catch
절을 찾지 못하면, 바로 다음 바깥쪽 try
문 다음에 위치한 catch
절을 차례대로 검사한다.
3-3] 이러한 과정을 가장 바깥쪽 try
문까지 계속 검사하게 된다.
3-4] 그래도 적절한 catch
절을 찾지 못하면, 미리 정의된 terminate()
함수가 호출된다.
4] 만약 적절한 catch
절을 찾게 되면, throw
문의 피연산자는 예외 객체의 형식 매개변수로 전달된다.
5] 모든 예외 처리가 끝나면 프로그램의 제어는 맨 마지막 catch
절 바로 다음으로 이동한다.
4) 스택 풀기
- 앞서 살펴본 예외 메커니즘의 (3-1)부터 (3-4)까지의 과정을 스택 풀기라고 한다.
- 스택 풀기란 예외를 처리하는 영역을 찾지 못해서 해당 예외가 호출된 영역을 상위 함수로 예외가 계속해서 전달되는 현상을 가리킨다.
#include <iostream>
using namespace std;
void Func03() { throw 0; }
void Func02() { Func03(); }
void Func01() { Func02(); }
int main(void) {
try {
Func01();
} catch (int ex) {
cout << "예외 처리(main): " << ex << endl;
}
return 0;
}
// 예외 처리(main): 0
- 위의 코드에서는 연속된 함수 호출을 통해 호출된
Func03()
함수에서 예외가 발생한다. - 하지만
Func03()
함수에는 예외를 처리할catch
절이 없으므로, 프로그램은Func02()
함수로 예외를 전달한다. - 이때 스택에 저장되어 있던
Func03()
함수에 관한 스택 프레임을 모두 인출(Pop)하고Func02()
함수로 이동한다. - 하지만
Func02()
함수에서도 예외를 처리할 수 없으므로, 또다시Func01()
함수로 예외를 전달한다. Func01()
함수에도 예외를 처리할catch
절이 없기 때문에 마지막으로Func01()
함수를 호출한main()
함수로 전달된다.- 따라서 예외의 실제 처리는
main()
함수 내의catch
절에서 수행되게 된다.
3. exception
클래스
1) 예외 클래스
- C++은 여러 예외를 처리하기 위해
exception
헤더 파일을 통한 다양한 예외 클래스를 제공하고 있다. - 이러한 예외 클래스는 오류 코드값을 가지는 멤버 변수 및 오류 코드를 검사하거나 오류 메시지를 출력하는 멤버 함수 등 오류에 대한 모든 처리가 가능하도록 다양한 멤버를 포함하고 있다.
- 이와 같은 예외 클래스도 클래스이므로, 상속할 수도 있으며 다형성도 성립한다.
- 또한, C++에서는 생성자와 연산자에서도 예외 처리 기능을 사용할 수 있다.
2) exception
클래스
- C++은 여러 예외 클래스의 기초 클래스로 사용할 수 있는
exception
클래스를 제공한다. exception
클래스는 시스템에 따라 하나의 문자열 포인터를 반환하는what()
이라는 가상 멤버 함수를 제공한다.- 이 멤버 함수는 가상 함수이므로,
exception
클래스로부터 파생된 클래스 내에서 재정의할 수 있다. exception
클래스의what()
멤버 함수는 별다른 일을 하지는 않지만, 파생 클래스에서는 원하는 문자열을 출력할 수 있도록 재정의할 수 있다.
3) 표준 예외 클래스
- 표준 C++ 라이브러리는
exception
클래스로부터 파생된 다양한 표준 예외 클래스를 정의하고 있다. - 이러한 표준 예외 클래스는
stdexcept
헤더 파일을 통해 제공되며, 논리 오류와 런타임 오류로 나눌 수 있다.
stdexcept
헤더 파일은 우선 다른 표준 예외 클래스의 기초 클래스가 되는 두 개의 클래스를 정의한다.
1] logic_error
- 일반적인 논리에 관한 오류들을 처리할 수 있음
2] runtime_error
- 프로그램이 실행되는 동안 발생할 수 있는 다양한 오류들을 처리할 수 있음
4) 처리되지 않은 예외
- C++ 프로그램은 발생한 예외를 처리할
catch
절을 찾을 수 없을 때, 미리 정의된terminate()
함수를 호출한다. - 이
terminate()
함수는 기본적으로abort()
함수를 호출하여 프로그램을 강제로 종료시킨다. - 하지만
set_terminate()
함수를 사용하면 이러한terminate()
함수의 기본 동작을 변경할 수 있다.
- 다음의 코드는
set_terminate()
함수를 이용해 처리되지 않은 예외를 위한 함수를 재정의하는 것이다.
#include <exception>
#include <iostream>
using namespace std;
void MyErrorHandler() {
cout << "처리되지 않은 예외가 발생했습니다." << endl;
exit(-1);
}
int main(void) {
set_terminate(MyErrorHandler);
try {
throw 1;
} catch (char* const ex) {
// 이 catch 절은 정수형 예외를 처리할 수 없음
}
return 0;
}
// 처리되지 않은 예외가 발생했습니다.
try
문에서는 정수형 예외가 발생했지만, 아래의catch
절은 해당 에외를 처리하지 못한다.- 따라서 발생한 예외는 처리되지 않으며, 따라서
set_terminate()
함수로 미리 정의한 내용이 출력되게 된다.
5) 예외 처리 시 주의 사항
- 예외가 발생하여 실행되고 있던 프로그램이 중지되는 현상은 매우 심각한 상황이다.
- 따라서 발생한 예외를 처리하는 노력은 매우 중요하다.
- 하지만 이러한 예외 처리에는 개발자의 많은 노력이 필요하게 된다.
- 또한, C++의 예외 처리 기능을 사용하면 프로그램의 크기가 커지고 실행 속도가 크게 떨어지는 단점이 있다.
- 따라서 무조건 발생할 수 있는 모든 예외를 다 처리하는 것이 아니라, 적당한 타협점을 찾는 게 중요하다.