본문 바로가기
C++

C++ 예외처리

by FraisGout 2020. 7. 6.

예외(exception)?

예외(exception)란 컴퓨터 시스템이 동작하는 도중에 예상하지 못한 오류가 발생하여, 실행되고 있던 프로그램이 중지되는 것을 의미합니다.

 

예외 처리(exception handling)는 이러한 예외 상황을 처리할 수 있도록 코드의 흐름을 바꾸는 행위를 의미합니다.

 

C++은 언어 차원에서 예외 처리 문법을 제공하여, 예외 처리하는 방식을 확장하고 관리하기 쉽도록 해줍니다.

 

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;

 

 

 

cout << "양의 정수를 하나 입력해주세요 : ";

 

while (cin >> num)

 

{

 

try

 

{

 

cout << "입력한 정수에서 1을 증가시킨 값 : " << IncreaseNumber(num) << endl;

 

}

 

catch (int input)

 

{

 

cout << input << "은 양의 정수가 아닙니다." << endl;

 

cout << "양의 정수를 다시 입력해주세요 : ";

 

continue;

 

}

 

catch(const char* st)

 

{

 

cout << st << endl;

 

cout << "양의 정수를 다시 입력해주세요 : ";

 

continue;

 

}

 

break;

 

}

 

return 0;

 

}

 

 

위의 예제처럼 throw 문의 피연산자는 변수와 문자열을 포함한 모든 타입의 예외를 사용할 수 있습니다.

 

하지만 C++ 표준에서는 throw 문의 피연산자로 std::exception에서 파생되는 예외 타입을 사용하도록 권장하고 있습니다.

 

예외 메커니즘

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 절 바로 다음으로 이동합니다.

 

스택 풀기(stack unwinding)

앞서 살펴본 예외 메커니즘의 3-1부터 3-4까지의 과정을 스택 풀기(stack unwinding)라고 합니다.

 

스택 풀기란 예외를 처리하는 영역을 찾지 못해서 해당 예외가 호출된 영역의 상위 함수로 예외가 계속해서 전달되는 현상을 가리킵니다.

 

 

 

예제

void Func03() { throw 0;}

 

void Func02() { Func03(); }

 

void Func01() { Func02(); }

 

 

 

int main(void)

 

{

 

try

 

{

 

Func01();

 

}

 

catch (int ex)

 

{

 

cout << "예외 처리(main) : " << ex << endl;

 

}

 

return 0;

 

}

 

 

 

위의 예제에서는 연속된 함수 호출을 통해 호출된 Func03() 함수에서 예외가 발생합니다.

 

하지만 Func03() 함수에는 예외를 처리할 catch 절이 없으므로, 프로그램은 Func02() 함수로 예외를 전달합니다.

 

이때 스택에 저장되어 있던 Func03() 함수에 관한 스택 프레임을 모두 인출(pop)하고 Func02() 함수로 이동합니다.

 

 

 

하지만 Func02() 함수에서도 예외를 처리할 수 없으므로, 또다시 Func01() 함수로 예외를 전달합니다.

 

Func01() 함수에도 예외를 처리할 catch 절이 없기 때문에 마지막으로 Func01() 함수를 호출한 main() 함수로 전달됩니다.

 

따라서 예외의 실제 처리는 main() 함수 내의 catch 절에서 수행되게 됩니다.

 

 

예외 클래스

C++은 여러 예외를 처리하기 위해 exception 헤더 파일을 통한 다양한 예외 클래스를 제공하고 있습니다.

 

이러한 예외 클래스는 오류 코드값을 가지는 멤버 변수 및 오류 코드를 검사하거나 오류 메시지를 출력하는 멤버 함수 등 오류에 대한 모든 처리가 가능하도록 다양한 멤버를 포함하고 있습니다.

 

 

 

이와 같은 예외 클래스도 클래스이므로, 상속할 수도 있으며 다형성도 성립합니다.

 

또한, C++에서는 생성자와 연산자에서도 예외 처리 기능을 사용할 수 있습니다.

 

exception 클래스

C++은 여러 예외 클래스의 기초 클래스로 사용할 수 있는 exception 클래스를 제공합니다.

 

exception 클래스는 시스템에 따라 하나의 문자열 포인터를 반환하는 what()이라는 가상 멤버 함수를 제공합니다.

 

이 멤버 함수는 가상 함수이므로, exception 클래스로부터 파생된 클래스 내에서 재정의할 수 있습니다.

 

exception 클래스의 what() 멤버 함수는 별다른 일을 하지는 않지만, 파생 클래스에서는 원하는 문자열을 출력할 수 있도록 재정의할 수 있습니다.

 

표준 예외 클래스

표준 C++ 라이브러리는 exception 클래스로부터 파생된 다양한 표준 예외 클래스를 정의하고 있습니다.

 

이러한 표준 예외 클래스는 stdexcept 헤더 파일을 통해 제공되며, 논리 오류와 런타임 오류로 나눌 수 있습니다.

 

stdexcept 헤더 파일은 우선 다른 표준 예외 클래스의 기초 클래스가 되는 두 개의 클래스를 정의합니다.

 

 

 

1. logic_error

 

2. runtime_error

 

 

 

logic_error 클래스는 일반적인 논리에 관한 오류들을 처리할 수 있습니다.

 

runtime_error 클래스는 프로그램이 실행하는 동안 발생할 수 있는 다양한 오류들을 처리할 수 있습니다.

 

처리되지 않은 예외

C++ 프로그램은 발생한 예외를 처리할 catch 절을 찾을 수 없을 때, 미리 정의된 terminate() 함수를 호출합니다.

 

terminate() 함수는 기본적으로 abort() 함수를 호출하여 프로그램을 강제로 종료시킵니다.

 

하지만 set_terminate() 함수를 사용하면 이러한 terminate() 함수의 기본 동작을 변경할 수 있습니다.

 

 

 

다음 예제는 set_terminate() 함수를 이용해 처리되지 않은 예외를 위한 함수를 재정의하는 예제입니다.

 

예제

#include <iostream>

 

#include <exception>

 

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() 함수로 미리 정의한 내용이 출력되게 됩니다.

 

예외 처리 시 주의 사항

예외가 발생하여 실행되고 있던 프로그램이 중지되는 현상은 매우 심각한 상황입니다.

 

따라서 발생한 예외를 처리하는 노력은 매우 중요합니다.

 

 

 

하지만 이러한 예외 처리에는 개발자의 많은 노력이 필요하게 됩니다.

 

또한, C++의 예외 처리 기능을 사용하면 프로그램의 크기가 커지고 실행 속도가 크게 떨어지는 단점이 있습니다.

 

 

 

따라서 무조건 발생할 수 있는 모든 예외를 다 처리하는 것이 아니라, 적당한 타협점을 찾는 게 중요합니다.

'C++' 카테고리의 다른 글

C++ 입력 & 출력  (0) 2020.07.06
C++ STL알고리즘  (0) 2020.07.05
C++ STL컨테이너  (0) 2020.07.05
C++ STL반복자  (0) 2020.07.05
C++ 템플릿  (0) 2020.07.05

댓글