..

Search

77) try, throw, catch

77) try, throw, catch

try, throw, catch


예외(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;

}

실행 결과

양의 정수를 하나 입력해주세요 : -3

-3은 양의 정수가 아닙니다.

 

양의 정수를 하나 입력해주세요 : 7

입력한 정수에서 1을 증가시킨 값 : 8

 

위의 예제처럼 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;

}

코딩연습 ▶

실행 결과

예외 처리(main) : 0

 

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

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

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

 

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

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

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


연습문제