Home [C++ 프로그래밍] 15강 - 예외 처리
Post
Cancel

[C++ 프로그래밍] 15강 - 예외 처리

💡해당 게시글은 방송통신대학교 이병래 교수님의 'C++ 프로그래밍' 강의를 개인 공부 목적으로 메모하였습니다.



학습 개요


  • 프로그램이 동작하는 상황은 항상 정상적인 것은 아니며, 때로는 정상적인 동작을 할 수 없는 상황이 발생할 수 있음
  • 이러한 경우를 고려하지 않고 프로그램을 작성하면 프로그램이 실행되는 동안 여러 가지 문제가 발생하여 비정상적인 에러가 발생하게 됨
  • 그러므로 프로그램을 설계할 때는 정상적이지 않은 상황이 발생하는 것을 고려하여 이에 적절히 대처할 수 있도록 대비하는 것이 필요함
  • 이러한 처리를 예외 처리라고 하며 C++에서는 예외를 처리하기 위한 문장과 클래스를 제공함



학습 목표


  • 예외의 개념을 설명할 수 있음
  • C++ 언어의 예외 처리 체계를 설명할 수 있음
  • 예외 처리를 위한 구문을 사용하여 정상적이지 않은 상황에 대처할 수 있는 프로그램을 작성할 수 있음
  • 예외 처리를 클래스를 이용하여 다양한 상황에 대한 예외의 발생 및 관련 데이터를 전달할 수 있는 프로그램을 작성할 수 있음



주요 용어


  • 예외(exception)
    • 프로그램이 실행되는 도중에 발생할 수 있는 비정상적인 상황
  • 예외 처리(exception handling)
    • 프로그램 실행 중 예외가 발생하였을 때를 대비하여 마련해 놓은 처리 절차에 따라 대응하는 것
  • 활성화 레코드(activation record)
    • 함수가 호출되었을 때 복귀할 지점, 매개 변수, 지역 변수 등을 저장하는 데이터 집합
  • 스마트 포인터(smart pointer)
    • 자동적인 메모리 관리 기능 등이 추가된 포인터를 구현하는 추상 데이터 타입
  • 예외 처리 클래스
    • 예외 상황 발생 및 이에 대한 구체적 원인 등을 예외 처리 블록에 전달하기 위한 목적으로 선언한 클래스



강의록


예외의 개념

예외

  • 예외(exception)란?
    • 프로그램 실행 도중에 발생할 수 있는 비정상적인 사건
      • 비정상적인 데이터, 자원의 부족 등
    • 예외 상황에 대한 적절한 대비를 하지 않으면 프로그램이 안정적으로 실행되지 않는 문제가 일어날 수 있음
  • 예외 처리(exception handling)
    • 프로그램 실행 중 예외가 발생하였을 때를 대비하여 마련해 놓은 처리 절차에 따라 대응하는 것

예외가 발생하는 상황의 예

  • 정상적인 처리를 할 수 없는 데이터
    • 기본 코드

      1
      2
      3
      4
      
        double hmean(double a, double b) // 조화평균
        {
          return 2.0 * a * b / (a + b);
        }
      
      • a == -b인 경우 실행 중 프로그램 비정상 종료
    • exit()사용

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
        double hmean(double a, double b) // 조화평균
        {
          if (a == -b)
          {
            cout << "나누기를 할 수 없습니다." << endl;
            exit(EXIT_FAILURE); // 프로그램 강제 종료
          }
          return 2.0 * a * b / (a + b);
        }
      
      • a == -b인 경우 이를 알리고, 프로그램 강제 종료
  • 프로그램이 요청하는 자원을 할당할 수 없는 경우
    • new(nothrow)사용

      1
      2
      3
      4
      
        void f() {
          int *p = new(nothrow) int[100000000];
          // 할당된 메모리의 활용
        }
      
      • 메모리 할당에 실패한 경우 실행 중 에러가 발생하여 프로그램이 비정상적으로 종료함
    • new(nothrow)를 이용한 메모리 할당 오류 검사

      1
      2
      3
      4
      5
      6
      7
      
        void f() {
          int *p = new(nothrow) int[100000000];
          if (!p) { // p가 nullptr이면
            cerr << "메모리 할당 오류" << endl;
            exit(EXIT_FAILURE);
          }
        }
      
      • 메모리 할당이 이루어지지 않은 경우 new(nothrow)nullptr를 반환하므로, 이를 이용하여 메모리 할당 오류를 검사함

C++ 언어의 예외 처리 체계

C++ 언어의 예외 처리 구문

  • try블록, catch블록, 그리고 throw문장으로 구성
  • 예외를 처리하는 함수

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      RetType1 someFunction()
      {
        try {
          // 예외가 발생할 수 있는 부분
          someDangerousFunction();
        }
        catch (eClass e) {
          // 발생한 예외를 처리하는 부분
          exceptionProcRtn();
        }
        
      }
    
  • 예외를 던지는 함수

    1
    2
    3
    4
    5
    6
    7
    
      RetType2 someDangerousFunction()
      {
        if (예외검출조건)
          throw eObj; // 예외 발생을 알림
        else
            // 정상적인 처리
      }
    

예외 처리의 예 - HMean.cpp

  • HMean.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
      #include <iostream>
      using namespace std;
        
      double hmean(double a, double b) // 조화평균
      {
        if (a == -b) // 예외 검출
          throw "조화평균을 계산할 수 없습니다!";
        return 2.0 * a * b / (a + b);
      }
        
      int main()
      {
        double x, y, z;
        char cFlag = 'y';
        while (cFlag != 'n')
        {
          cout << "\\n두 수를 입력하시오 : ";
          cin >> x >> y;
          try { // 예외 발생 가능성이 있는 코드
            z = hmean(x, y);
            cout << "조화평균 = " << z << endl;
          }
          catch (const char* s) { // 예외 처리
            cout << s << endl;
          }
          cout << "계속 할까요? (y/n) : ";
          cin >> cFlag;
        }
        return 0;
      }
    

예외 유형 별 처리

  • 하나의 try블록에 여러 개의 catch블록 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      try {
        // 예외가 발생할 가능성이 있는 함수 호출
      }
      // throw된 객체의 자료형에 맞는 매개변수가 선언된 catch 블록에서 예외 처리함
      catch (eClass1 e) {
        // 예외 처리 블록1
      }
      catch (eClass2 e) {
        // 예외 처리 블록2
      }
      catch (...) {
        // 그 외의 모든 예외 처리
      }
      // 다음 문장
    

제어의 전달

  • 정상적인 제어의 흐름

    image.png

    • 정상적인 호출 및 복귀 절차에 따라 실행됨
  • 예외가 발생한 경우 제어의 흐름

    image.png

    • 발생된 예외를 처리하기 위한 catch블록으로 함수 호출 스택을 따라 이동

예외 처리에 따른 자원 관리 문제

  • 자원 소실이 가능한 상황

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      void f()
      {
        int *p = new int[1000];
        for (int i = 0; i < 1000; i++)
          p[i] = i;
        
        if (ex_condition)
          throw "exception";
        
        delete[] p; // throw 명령이 실행되면 나머지 문장들은 실행되지 않음
      }
    
    • 예외를 처리할 catch블록으로 복귀할 때 f()가 호출될 때까지 거쳐온 함수들의 지역 변수들은 정상적인 소멸 과정을 거침
  • 스마트 포인터의 활용
    • unique_ptr
      • 할당된 메모리를 한 개의 포인터만 가리킬 수 있음
      • 다른 unique_ptr에 대입할 수 없으며, 이동 대입만 할 수 있음
      • unique_ptr가 제거되거나 nullptr를 대입하면 가리키고 있던 메모리를 반납함
    • shared_ptr
      • 할당된 메모리를 여러 개의 포인터로 가리킬 수 있음
      • 다른 shared_ptr에 대입 및 이동 대입 가능
      • 포인터가 제거되거나 nullptr를 대입하는 등의 처리로 그 메모리를 가리키는 shared_ptr이 더 이상 없으면 메모리를 반납함
  • 스마트 포인터의 활용 예

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      #include <iostream>
      #include <memory>
      using namespace std;
        
      int main()
      {
        unique_ptr<int> p1{ new int };
        unique_ptr<int> p2;
        *p1 = 10;
        cout << *p1 << endl;
        p2 = move(p1); // p2 = p1;은 불가
        cout << *p2 << endl;
        p2 = nullptr; // 가리키고 있던 메모리는 해제됨
        return 0;
      }
    
  • 예외 처리로 인한 자원 소실 방지
    • unique_ptr활용

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        #include <memory>
        void f()
        {
          std::unique_ptr<int[]> p { new int[1000] };
          for (int i = 0; i < 1000; i++)
            p[i] = i;
              
          if (ex_condition)
            throw "exception";
              
        }
      
    • vector활용

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        #include <vector>
        void f()
        {
          std::vector<int> p(1000);
          for (int i = 0; i < 1000; i++)
            p[i] = i;
              
          if (ex_condition)
            throw "exception";
              
        }
      

noexcept지정자

  • noexcept함수 지정
    • 함수가 예외를 일으키지 않음을 지정
    1
    2
    3
    4
    5
    6
    7
    8
    
      template <typename T>
      T max(const vector<T>& v) noexcept {
        auto p = v.begin();
        T m = *p;
        for (; p != v.end(); p++)
          if (m < *p) m = *p;
        return m;
      }
    

예외 처리 클래스

클래스에서 예외 처리 활용하기

  • 예외 처리 클래스의 활용
    • 클래스 설계 시 예외 처리 기능을 포함 시킴으로써 객체에서 예외가 발생하였을 때 그 위치나 원인 등의 식별을 용이하게 할 수 있음
    • 클래스 선언문 내에 예외 처리 담당 클래스를 선언하여 활용함

클래스의 예외 처리 활용 예 - IntArray1.h

  • 클래스 정의

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      const int DefaultSize = 10;
      class Array {
        int *buf;
        int size;
      public:
        Array(int s = DefaultSize);
        virtual ~Array() { delete[] buf; }
        int& operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset);
        const int& operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset) const;
        int getsize() const { return size; }
        friend ostream& operator<<(ostream&, Array&);
        class BadIndex {}; // exception class
      };
    

클래스의 예외 처리 활용 예 - IntArray1.cpp

  • 생성자 및 operator[]구현

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      #include "IntArray1.h"
      using namespace std;
        
      Array::Array(int s) : size(s)
      {
        buf = new int[s];
        memset(buf, 0, sizeof(int) * s);
      }
        
      int& Array::operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset)
      {
        if (offset < 0 || offset >= size) // 예외조건 검사
          throw BadIndex(); // 예외객체 생성 및 전달
        return buf[offset];
      }
    

클래스의 예외 처리 활용 예 - IA1Main.cpp

  • 메인 함수 및 예외 처리

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      #include <iostream>
      #include "IntArray1.h"
      using namespace std;
        
      int main()
      {
        Array arr(10);
        try {
          for (int i = 0; i < 10; i++)
            arr[i] = i;
          // arr[10] = 10; // 인덱스 범위를 벗어나는 접근
        }
        catch (Array::BadIndex e) {
          cerr << "인덱스 범위 오류" << endl;
        }
        cout << arr << endl;
        return 0;
      }
    

예외 객체의 멤버를 통한 예외 정보 전달

  • IntArray1.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      class Array {
      public:
        
        class BadIndex {
        public:
          int wrongIndex;
          BadIndex(int n) : wrongIndex(n) {}
        };
      };
    
  • IntArray1.cpp

    1
    2
    3
    4
    5
    
      int& Array::operator[](/assets/img/knou/cpp/2025-07-02-knou-cpp-15/int offset) {
        if (offset < 0 || offset >= size)
          throw BadIndex(offset);
        return buf[offset];
      }
    
  • IA1Main.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      int main()
      {
        // Array 객체 사용
        // 
        try {
            // 
        }
        catch (Array::BadIndex e) {
            cout << "인덱스 범위 오류 --> "
                 << e.wrongIndex << endl;
        }
        // 
      }
        
    

exception클래스

  • exception클래스
    • C++ 언어에서 예외를 처리하기 위해 예외 처리 담당 클래스의 기초 클래스를 제공하는 클래스
    • 헤더 파일 <exception>을 소스 파일에 포함 시킴
    • 가상 함수 what()을 멤버 함수로 가지고 있음
      • 예외의 종류를 char*형태로 반환함
      • exception의 파생 클래스에서 재 정의하여 사용함
  • IntArray1.h 수정

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      #include <exception>
        
      class Array {
        
      public:
        
        class BadIndex : public exception {
        public:
          int wrongIndex;
          BadIndex(int n) : wrongIndex(n), exception() {}
          const char* what() const {
            return "Array Exception::";
          }
        };
      };
    

예외 객체의 다시 던지기

  • catch블록에서 처리를 완결 할 수 없는 예외의 전달
    • 현 단계의 catch블록에서 처리를 완결할 수 없는 예외에 대한 후속 처리를 할 수 있게 예외 객체를 다시 throw할 수 있음
1
2
3
4
5
6
7
8
9
class ExceptionClass
:public exception {

};

int f(int a) {
  if (a < 0)
    throw ExceptionClass();
}
1
2
3
4
5
6
7
8
9
10
int g(int x) {
  try {
    f(x);

  }
  catch (ExceptionClass e) {
    // 현 단계의 예외 처리
    throw; // 예외 객체를 다시 던지기
  }
}
1
2
3
4
5
6
7
8
9
int h(int c) {
  try {
    g(c);

  }
  catch (ExceptionClass e) {
    throw; // 후속 예외 처리
  }
}



연습 문제


  1. 위 지문과 같이 함수가 정의되어 있다. condition이 false일 경우 함수 f()에 의해 출력되는 내용은?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     class eCls { };
        
     void g()
     {
       if (condition)
         throw 10;
       else
         throw eCls();
     }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
     void f()
     {
       try {
         g();
       }
       catch (char* s)
       {
         cout << "예외1 발생";
       }
       catch (int e)
       {
         cout << "예외2 발생";
       }
       catch (...)
       {
         cout << "예외3 발생";
       }
     }
    

    a. 예외3 발생

    • condition이 false이면 함수 g()는 eCls 객체를 전달함
    • 함수 f()catch블록은 char*, int, 그외의 모든 예외 객체(‘…‘으로 표기함)에 대해 정의되어 있으며, eCls 객체는 char*도 아니고 int도 아니므로 catch (...)에 의해 처리 됨
  2. 다음 중 프로그램 실행 중 예외 발생하였음을 알리기 위해 사용하는 명령은?

    a. throw

    • 예외가 발생할 가능성이 있는 곳에서는 이를 검사하여 예외 상황에 해당되는 경우 throw명령으로 예외 객체를 던짐
    • 이렇게 발생한 예외를 처리하는 구문은 try블록과 catch블록으로 구성함
    • try블록에는 예외가 발생할 가능성이 있는 처리를 하는 부분이며, 만일 예외 상황에 해당되는 경우 throw명령으로 예외 객체를 던짐
    • 이 예외는 catch블록에서 선택적으로 받아 예외의 종류에 맞는 처리를 함
  3. 함수를 선언할 때 noexcept를 지정하는 것은 어떤 의미인가?

    a. 그 함수가 예외를 일으키지 않음을 지정함

    • noexcept는 함수가 예외를 발생 시키지 않는다는 것을 지정하기 위한 키워드임
    • 이것은 예외가 발생되지 않을 것이 확실한 경우 컴파일러가 예외 처리 시퀀스를 고려할 필요가 없음을 알림으로써 컴파일러가 최적화를 하는 데 도움을 줌
  4. 위 지문 함수 f()의 실행에 대한 설명으로 옳은 것은?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     class A {
        
     };
        
     void f( )
     {
        int *p = new int[100];
        A objA;
        
        if (ex_condition)
         throw "exception";
              
         delete[ ] p;
        
     }
    

    a. ex_condition이 참일 경우 할당된 메모리가 소실되는 문제가 있다.

    • ex_condition이 참이면 throw명령을 실행하게 되되, 이때 함수의 나머지 명령은 실행하지 않음
    • 따라서 new연산자로 동적으로 할당되어 p가 가리키고 있는 메모리에 대한 delete연산이 실행되지 않으며, 결과적으로 할당된 메모리를 어느 것도 가리키고 있지 않아 메모리 소실이 일어남
    • 그러나 throw명령으로 함수에서 나가는 경우에도 지역 변수는 제거되며, 이 과정에서 객체의 소멸자가 동작함



정리 하기


  • 예외란 프로그램이 실행되는 도중에 발생할 수 있는 비정상적인 사건을 의미함
  • try블록은 예외가 발생할 가능성이 있는 문장을 포함함
  • catch블록은 throw명령을 통해 전달 된 예외 객체의 유형에 따라 적절한 예외 처리를 하는 문장을 포함
  • 예외 상황이 발생되면 throw명령으로 이를 알림
  • 클래스 내에 예외 처리 담당 클래스를 선언하여 발생한 예외의 위치나 원인을 식별하게 할 수 있음
  • exception클래스는 예외 처리 담당 클래스의 기초 클래스로서, 예외의 종류를 char*형태로 반환하는 가상함수 what()을 포함함

[C++ 프로그래밍] 14강 - 템플릿

-