프로그램을 작성하다보면 의도치 않은 오류나 문제점이 발생하는 상황이 많다. 그런 상황에 유연하게 대응할 수 있도록 C++은 try ~catch와 같은 예외 메커니즘을 제공하고 있다. 이번 포스팅에서는 try catch의 사용 방법을 정리하려고 한다.

     


    목차

    • 예외 코드 작성법
    • 키워드 noexcept
    • 예외 메커니즘의 단점

     


    예외(exception) 코드 작성법

    보통 try ~catch문을 배우기 전에는 if문을 사용해 예외를 정해주고 있다. 다음은 1~9사이의 실수를 입력받고 그대로 출력해주는 코드이다. if문을 통해 실수 1~9까지의 범위를 초과하는 입력이 들어올 경우 다시 입력을 받는다.  

     

    if문을 사용한 예외 
    #include <iostream>
    using namespace std;
    
    int main()
    {
    	int number = 0;
    
    	cout << "1~9의 실수를 입력(종료 = q): ";
    	while (cin >> number)
    	{
    		if (number < 0 || number > 9)
    		{
    			cout << "실수 1 ~ 9 사이의 수만 입력이 가능합니다." << endl;
    			cout << "다시 입력(종료 = q): ";
    			continue;
    		}
    		cout << "입력된 수: " << number << endl;
    		cout << "다시 입력(종료 = q): ";
    	}
    	cout << "프로그램을 종료합니다." << endl;
    	return 0;
    }

     

     

     

    try ~catch를 사용한 예외 코드
    #include <iostream>
    using namespace std;
    
    void printNumber(int num);
    
    int main()
    {
    	int number = 0;
    
    	cout << "1~9의 실수를 입력(종료 = q): ";
    	while (cin >> number)
    	{
    		try
    		{
    			printNumber(number);
    		}
    		catch(const char* str)
    		{
    			cout << str << endl;
    		}
    		cout << "다시 입력(종료 = q): ";
    	}
    	cout << "프로그램을 종료합니다." << endl;
    	return 0;
    }
    
    void printNumber(int num)
    {
    	if (num < 0 || num > 9)
    		throw "실수 1 ~ 9 사이의 수만 입력이 가능합니다.";
    	else
    		cout << "입력된 수: " << num << endl;
    }

     

     

    • throw "실수 1 ~ 9 사이의 수만 입력이 가능합니다.";
      num를 매개변수로 넘겨받고 만약 num이 1~9의 범위밖에 있는 수일 경우 catch문으로 문자열 상수를 throw한다. 
    • catch(const char* str)
      위에 throw로 넘겨받은 문자열 상수의 주소를 const char* str로 가리킨 후 그대로 출력한다.

     

    throw는 함수의 return문처럼 데이터를 catch에게 넘겨준다. 여기서 throw는 함수의 return문과 동작이 비슷해보이지만 큰 차이점이 있다. return은 자신이 속한 scope의 함수만 종료시키고 값을 리턴하지만, throw는 try문이 나올때까지 scope를 종료시키며 계속 타고 올라간다. 이러한 특성을 스택풀기(unwinding the stack) 라고 한다.

     

    try
    {
      Func1(num);  // throw가 발생할 경우 ABCD 함수는 생략되고 catch문으로 넘어간다.
      ABCD(num);    
     }
    catch(exception& exp)
    {
       ...
    }
    
    // throw문은 return과 다르게 Func2, Func1을 모두 종료시킨다.
    void Func1(int num)
    {
       Func2(num)
       {
           throw exp;
       } 
    }

     

    여기서 catch문은 함수의 정의와 비슷해보이지만 함수의 정의가 아니다. 키워드 catch는 핸들러로 인식한다. const char* str은 핸들러가 문자열로 발생된 예외를 받아들인다는걸 의미한다. 

     

     

    abort() 함수

    만약 어떤 함수가 예외를 발생시키는데 try문이 없거나 데이터형에 맞는 catch 핸들러가 없다면 어떻게 될까? 이런 상황에는 프로그램은 기본적으로 abort() 함수를 호출한다. abort() 함수는 프로그램을 가동시킨 부모 프로세스나 운영체제에 컴파일러에 종속된 어떤 값을 메세지로 나타내고 파일버퍼를 비운다. 메세지가 있는 exit() 라고 보면 된다.

     

     

    예외(exception)를 객체로 대응하기

    throw로 문자열을 넘기듯이 throw는 객체또한 예외로 핸들러에게 전달할 수 있다. 다음은 객체를 이용한 예외 메커니즘을 구현한 코드이다.

    #include <iostream>
    using namespace std;
    
    // 예외를 위한 클래스
    class bad_numexp  
    {
    public:
    	void error()
    	{
    		cout << "실수 1 ~ 9 사이의 수만 입력이 가능합니다.\n";
    	}
    
    };
    
    void printNumber(int num);
    
    int main()
    {
    	int number = 0;
    
    	cout << "1~9의 실수를 입력(종료 = q): ";
    	while (cin >> number)
    	{
    		try
    		{
    			printNumber(number);
    			cout << "try문 종료" << endl;  // throw 발생시 생략된다.
    		}
    		catch(bad_numexp& exp)
    		{
    			exp.error();
    		}
    		cout << "다시 입력(종료 = q): ";
    	}
    	cout << "프로그램을 종료합니다." << endl;
    	return 0;
    }
    
    void printNumber(int num)
    {
    	if (num < 0 || num > 9)
    		throw bad_numexp();
    	else
    		cout << "입력된 수: " << num << endl;
    }

     

    • throw bad_numexp();
      예외를 관리하기 위한 클래스 bad_numexp() 생성자를 호출해서 임시 객체를 catch 핸들러에 throw한다.
    • exp.error();
      bad_numexp 클래스의 에러 문구를 출력하기 위한 멤버 함수를 호출한다.

     

    위 코드처럼 예외만을 관리하는 class를 이용해 에러 메세지를 모두 모아 관리해줄 수도 있다. 

     

     


     

    키워드 noexcept

    서두에서 if문을 예외로 사용할 경우 if문이 포함된 함수가 예외를 가진 함수인지 아니면 예외가 포함되지 않은 다른 목적을 가진 함수인지 불명확해서 try catch를 사용한다고 말했었다. 위에 코드처럼 printNumber함수는 예외가 존재하는 함수이다. 1~9 사이에 실수가 아니면 예외를 발생시킨다. 만약 예외를 발생시키지 않는 또 다른 함수가 있다면 어떻게 알 수 있을까? 함수의 내부를 살펴보는 수 밖에 없다. 해당 함수 내에서 마찬가지로 예외가 있는지 없는지 확인하려면 내부의 throw문이 있는지 일일히 확인해야한다. 그래서 보통 throw(); 라고 적어서 예외를 발생시키지 않는다라고 표기해주기도 하는데 이렇게 매번 내부를 일일히 확인할 수도 없는 노릇 아닌가? 그래서 C++은 noexcept 키워드를 제공한다. 함수의 선언부에 noexcept를 표기해주면 해당 함수는 throw같은 예외를 발생시키지 않는 함수라는 의미를 가지게 된다. 이렇게 되면 일일히 함수 내부를 뜯어보지 않아도 되니 보다 효율적으로 작업이 가능해진다.

     

     

     


     

    예외 메커니즘의 단점

    예외 처리는 어떤 프로젝트에서는 매우 중요한 수단이 될 수 있다. 하지만 예외 처리는 프로그래밍 작성에 상대적으로 많은 노력을 요구하며, 프로그램의 크기를 키우고 속도가 떨어지게 만든다. 그리고 동적 메모리 대입과 템플릿등 예외의 사용과 어울리지 않는 문법도 많으므로 사용시 주의를 요구한다.  

     

     

     


     

    '과거 자료' 카테고리의 다른 글

    [#3] Queue  (0) 2022.07.28
    [C++ Basic] Smart Pointer Template Class  (0) 2022.07.10
    [C++ Basic] 프렌드 클래스 (friend class)  (0) 2022.07.04
    [#2] Stack  (0) 2022.07.04
    [#1] Singly Linked List  (0) 2022.06.25