5 minute read

1. 예외상황과 예외처리 이해

✏️예외상황을 처리하지 않았을 때의 결과

  • 예외 : 프로그램의 논리에 맞지 않는 상황

  • 예외를 제대로 처리하지 않았을 때, 예외가 발생하면 그냥 프로그램이 종료되어 버린다.

  • if문을 사용해 예외처리를 할 수 있지만, 예외가 발생하는 위치와 예외가 발견되는 위치가 다를 수도 있다.
    따라서 if문을 예외처리를 위한 코드로 쓰기엔 직관적이지도 않고, 주석이 있다고 해도 프로그램 흐름을 이해하는 데 협조적이지는 않다.

2. C++ 예외처리 메커니즘

  • C++은 구조적으로 예외를 처리할 수 있는 메커니즘을 제공한다. 이를 통해 코드의 가독성과 유지보수성을 높일 수 있다.

✏️try, catch 그리고 throw

try : 예외 발견
catch : 예외 처리
throw : 예외 발생

  • try
    • 예외가 발생할 가능성이 있는 부분
  • catch
    • try에서 발생한 예외를 처리하는 부분
  • throw
    • 예외가 발생했음을 알리는 문장의 구성에 사용된다
    • throw expn;을 보고 예외라고 표현한다. expn은 변수, 상수, 객체 등 표현 가능한 예외상황 데이터가 될 수 있다.

      try{
          //일단 수행해보는데 예외가 발생할 수 있는 코드
          //예외가 발생하면
          throw expn;
      }
      catch(type expn){
          //처리 방법
      }
      

      throw에 의해 던져진 예외 데이터는 try블록에 의해 감지되어 catch에서 처리된다

  • 예제

      #include <iostream>
      using namespace std;
    
      int main(void)
      {
          int num1, num2;
          cout<<"두 개의 숫자 입력: ";
          cin>>num1>>num2;
    
          try             //try블록이 수행된다
          {
              if(num2==0)     //num2==0이면 참이되어 throw문을 수행하고 catch로 넘어간다
                  throw num2; //catch문에 num2가 전달된다
    
              cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
              cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
          }
          catch(int expn)
          {
              cout<<"제수는 "<<expn<<"이 될 수 없습니다."<<endl;
              cout<<"프로그램을 다시 실행하세요."<<endl;
          }
          cout<<"end of main"<<endl;
          return 0;
      }
    

✏️try 블록을 묶는 기준

  • 예외가 발생할만한 영역과 관련된 모든 문장을 묶어 “work” 단위로 구성한다

  • try 블록 내에서 예외가 발생하면 예외가 발생한 지점 이후 나머지 try 영역은 건너뛰기 때문에 예외가 발생할 부분만 묶는 게 아니다.

  • 예제

      #include <iostream>
      using namespace std;
    
      int main(void)
      {
          int num1, num2;
          cout<<"두 개의 숫자 입력: ";
          cin>>num1>>num2;
    
          try             //try블록이 수행된다
          {
              if(num2==0)     //num2==0이면 참이되어 throw문을 수행하고 catch로 넘어간다
                  throw num2; //catch문에 num2가 전달된다
          }
          catch(int expn)
          {
              cout<<"제수는 "<<expn<<"이 될 수 없습니다."<<endl;
              cout<<"프로그램을 다시 실행하세요."<<endl;
          }
            
          cout<<"나눗셈의 몫: "<< num1/num2 <<endl;       //이문장은 실행되면 안되는데 밖으로 빠져나와있어 예외처리 되고나서도 실행된다
          cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
            
          cout<<"end of main"<<endl;
          return 0;
      }
    

3. Stack Unwinding 스택풀기

✏️예외전달

  • 호출된 함수에서 throw절이 실행되면서 예외가 발생한 경우, 예외처리에 대한 책임은 이 함수를 호출한 영역으로 넘어간다

  • 예제

      #include <iostream>
      using namespace std;
    
      void Divide(int num1, int num2)
      {
          if(num2==0)
              throw num2;         
            
          cout<<"나눗셈의 몫: "<< num1/num2 <<endl;
          cout<<"나눗셈의 나머지: "<< num1%num2 <<endl;
      }
    
      int main(void)
      {
          int num1, num2;	
          cout<<"두 개의 숫자 입력: ";
          cin>>num1>>num2;
    
          try
          {
              Divide(num1, num2);
              cout<<"나눗셈을 마쳤습니다."<<endl;
          }
          catch(int expn)
          {
              cout<<"제수는 "<<expn<<"이 될 수 없습니다."<<endl;
              cout<<"프로그램을 다시 실행하세요."<<endl;
          }
    
          return 0;
      }
    
    • Divide()에서 발생한 예외에 대한 try, catch문이 존재하지 않기 때문에 이 함수를 호출한 main에 예외처리의 책임이 예외데이터와 함께 넘어간다

    • 함수 내에서 함수를 호출한 영역으로 예외 데이터를 전달하면, 해당 함수는 더 이상 실행되지 않고 종료된다

✏️스택풀기

  • 예외가 처리되지 않아 함수를 호출한 영역으로 예외 데이터가 전달되는 현상
  • 예제
      #include <iostream>
      using namespace std;
    
      void SimpleFuncOne(void);
      void SimpleFuncTwo(void);
      void SimpleFuncThree(void);
    
      int main(void)
      {
          try
          {
              SimpleFuncOne();
          }
          catch(int expn)
          {
              cout<<"예외코드: "<< expn <<endl;
          }
          return 0;
      }
    
      void SimpleFuncOne(void) 
      { 
          cout<<"SimpleFuncOne(void)"<<endl;
          SimpleFuncTwo(); 
      }
      void SimpleFuncTwo(void) 
      { 
          cout<<"SimpleFuncTwo(void)"<<endl;
          SimpleFuncThree(); 
      }
      void SimpleFuncThree(void) 
      { 
          cout<<"SimpleFuncThree(void)"<<endl;
          throw -1; 
      }
    
    
    • 스택과 같이 함수가 main -> one -> two -> three순으로 호출되었다가,
      예외발생으로 예외데이터와 처리책임이 three -> two -> one -> main으로 전달된다.

    • 예외가 처리되지 않아 예외데이터가 main까지 도달했는데도 try, catch가 없으면 terminate()함수가 호출되어 프로그램이 종료된다

✏️자료형이 일치하지 않아도

  • 데이터는 전달된다.
  • 그러나 자료형의 불일치로 인해 예외는 처리되지 않는다. 따라서 이 함수를 호출한 영역으로 예외데이터가 전달된다.

✏️try 한 개, 다수의 catch

  • 자료형에 따라 맞는 catch문으로 예외데이터가 전달된다

      #include <iostream>
      #include <cstring>
      #include <cmath>
      using namespace std;
    
      int StoI(char * str)
      {
          int len=strlen(str);
          int num=0;
    
          if(len!=0 && str[0]=='0')
              throw 0;
    
          for(int i=0; i<len; i++)
          {
              if(str[i]<'0' || str[i]>'9')
                  throw str[i];
    
              num += (int)(pow((double)10, (len-1)-i) * (str[i]+(7-'7')));
          }
          return num;	
      }
    
      int main(void)
      {
          char str1[100];
          char str2[200];
    
          while(1)
          {
              cout<<"두 개의 숫자 입력: ";
              cin>>str1>>str2;
    
              try
              {
                  cout<<str1<<" + "<<str2<<" = "<<StoI(str1)+StoI(str2)<<endl;
                  break;
              }
              catch(char ch)
              {
                  cout<<"문자 "<< ch <<"가 입력되었습니다."<<endl;
                  cout<<"재입력 진행합니다."<<endl<<endl;
              }
              catch(int expn)
              {
                  if(expn==0)
                      cout<<"0으로 시작하는 숫자는 입력불가."<<endl;
                  else
                      cout<<"비정상적 입력이 이루어졌습니다."<<endl;
    
                  cout<<"재입력 진행합니다."<<endl<<endl;
              }
          }
          cout<<"프로그램을 종료합니다."<<endl;
          return 0;
      }
    

✏️예외 명시

  • 정의된 특정함수의 호출을 위해 함수의 이름, 매개변수, 반환형에 더해, 함수 내에서 전달될 수 있는 예외 종류와 상황도 명시해주는 것이 좋다.

      int ThrowFunc(int num) throw(int, char){
          ///코드
      }
    
  • thorw() 비어있는 경우에는 어떠한 예외도 전달하지 않음을 의미하고, 따라서 이 함수가 예외를 전달할 경우 프로그램은 그냥 종료된다.

4. 예외 클래스 설계

예외객체: 예외 발생을 알리는데 사요오디는 객체 예외클래스: 예외객체 생성을 위해 정의된 클래스

  • 예제
      #include <iostream>
      #include <cstring>
      using namespace std;
    
      class DepositException
      {
      private:
          int reqDep;		// 요청 입금액
      public:
          DepositException(int money) : reqDep(money)
          { }
          void ShowExceptionReason()
          {
              cout<<"[예외 메시지: "<<reqDep<<"는 입금불가]"<<endl;
          }
      };
    
      class WithdrawException
      {
      private:
          int	balance;	// 잔고
      public:
          WithdrawException(int money) : balance(money)
          { }
          void ShowExceptionReason()
          {
              cout<<"[예외 메시지: 잔액 "<<balance<<", 잔액부족]"<<endl;
          }
      };
    
      class Account
      {
      private:
          char accNum[50];	// 계좌번호
          int	balance;		// 잔고
      public:
          Account(char * acc, int money) : balance(money)
          {
              strcpy(accNum, acc);
          }
          void Deposit(int money) throw (DepositException)
          {
              if(money<0)
              {
                  DepositException expn(money);
                  throw expn;	
              }
              balance+=money;
          }
          void Withdraw(int money) throw (WithdrawException)
          {
              if(money>balance)
                  throw WithdrawException(balance);
              balance-=money;
          }
          void ShowMyMoney()
          {
              cout<<"잔고: "<<balance<<endl<<endl;
          }
      };
    
      int main(void)
      {
          Account myAcc("56789-827120", 5000);
    
          try
          {
              myAcc.Deposit(2000);
              myAcc.Deposit(-300);
          }
          catch(DepositException &expn)
          {
              expn.ShowExceptionReason();
          }
          myAcc.ShowMyMoney();
    
          try
          {
              myAcc.Withdraw(3500);
              myAcc.Withdraw(4500);
          }
          catch(WithdrawException &expn)
          {
              expn.ShowExceptionReason();
          }	
          myAcc.ShowMyMoney();
          return 0;
      }
    
  • 예외 클래스에도 상속될 수 있다. 코드가 좀 더 간단해진다

      class AccountException
      {
      public:
          virtual void ShowExceptionReason() =0;
      };
    
      class DepositException : public AccountException
      {
      private:
          int reqDep;		// 요청 입금액
      public:
          DepositException(int money) : reqDep(money)
          { }
          void ShowExceptionReason()
          {
              cout<<"[예외 메시지: "<<reqDep<<"는 입금불가]"<<endl;
          }
      };
    
      class WithdrawException : public AccountException
      {
      private:
          int	balance;	// 잔고
      public:
          WithdrawException(int money) : balance(money)
          { }
          void ShowExceptionReason()
          {
              cout<<"[예외 메시지: 잔액 "<<balance<<", 잔액부족]"<<endl;
          }
      };
    
      class Account
      {
      private:
          char accNum[50];	// 계좌번호
          int	balance;		// 잔고
      public:
          Account(char * acc, int money) : balance(money)
          {
              strcpy(accNum, acc);
          }
          void Deposit(int money) throw (AccountException)
          {
              if(money<0)
              {
                  DepositException expn(money);
                  throw expn;	
              }
              balance+=money;
          }
          void Withdraw(int money) throw (AccountException)
          {
              if(money>balance)
                  throw WithdrawException(balance);
              balance-=money;
          }
          void ShowMyMoney()
          {
              cout<<"잔고: "<<balance<<endl<<endl;
          }
      };
    
      int main(void)
      {
          Account myAcc("56789-827120", 5000);
    
          try
          {
              myAcc.Deposit(2000);
              myAcc.Deposit(-300);
          }
          catch(AccountException &expn)
          {
              expn.ShowExceptionReason();
          }
          myAcc.ShowMyMoney();
    
          try
          {
              myAcc.Withdraw(3500);
              myAcc.Withdraw(4500);
          }
          catch(AccountException &expn)
          {
              expn.ShowExceptionReason();
          }	
          myAcc.ShowMyMoney();
          return 0;
      }
    

5. 예외처리의 또다른 특성

  • new 연산자에 의해 발생하는 예외
    • new 연산에 의한 메모리 공간 할당이 실패하면 bad_alloc이라는 예외가 발생한다.
  • 모든 예외를 처리하는 catch블록
    try{
    
    }catch(...){
    
    }
    
    • 이렇게 catch블록을 선언하면 try에서 전달되는 모든 예외가 자료형 상관없이 걸려든다
  • 예외 여러번 던지기
    • catch블록에 던져진 예외는 다시 던져질 수 있다. 따라서 하나의 예외가 둘 이상의 catch블록에 의해 처리되게 할 수 있다

Tags: ,

Categories:

Updated: