C++/Exception 2021. 3. 22. 00:13

예외 처리의 위험성과 단점

메모리 누수

  • throw 이후의 코드는 무시되기 때문에, 메모리 누수가 발생할 가능성이 존재한다.

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      try
      {
        int* i = new int[100000];
    
        throw "error";
    
        delete[] i;
      }
      catch (...)
      {
        cout << "Catch\n";
      }
    }
    
    /* stdout stderr
    Catch
    */
    • unique_ptr를 사용하여 해결할 수 있다.

소멸자에서 throw 사용 금지

  • 소멸자에서는 throw의 사용이 금지되어있다.

    • 만약 사용하면 런타임 에러가 발생한다.
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class A
    {
    public:
      ~A()
      {
        throw "error";
      }
    };
    
    int            main()
    {
      try
      {
        A a;
      }
      catch (...)
      {
        cout << "Catch\n";
      }
    }
    warning C4297: 'A::~A': function assumed not to throw an exception but does
    message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
C++/Exception 2021. 3. 22. 00:12

함수 try (Function try)

함수의 body 자체가 try, catch로 이루어진 경우

  • 함수를 정의할 때 중괄호로 감싸지 않아도 된다고 한다.

  • 잘 쓰이는 문법은 아닌 것 같다.

  • rethrow를 하지 않는다.

    #include <iostream>
    
    using namespace std;
    
    void        doSomething()
    try
    {
      throw - 1;
    }
    catch (...)
    {
      cout << "Catch in doSomething()\n";
    }
    
    int            main()
    {
      try
      {
        doSomething();
      }
      catch (...)
      {
        cout << "Catch in main()\n";
      }
    }
    
    /* stdout stderr
    Catch in doSomething()
    */

클래스의 생성자에서 예외가 발생할 경우

  • 생성자의 멤버 초기화 리스트까지 포함하여 try를 시도하고, 바로 아래에서 catch한다.

  • 자동으로 rethrow를 적용한다.

    #include <iostream>
    
    using namespace std;
    
    class A
    {
      int    x_;
    
    public:
      A(int x) : x_(x)
      {
        if (x <= 0)
          throw 1;
      }
    };
    
    class B : public A
    {
    public:
      B(int x) try : A(x)
      {}
      catch (...)
      {
        cout << "Catch in B constructor\n";
        //throw;
      }
    };
    
    int            main()
    {
      try
      {
        B b(0);
      }
      catch (...)
      {
        cout << "Catch in main()\n";
      }
    }
    
    /* stdout stderr
    Catch in B constructor
    Catch in main()
    */
C++/Exception 2021. 3. 22. 00:10

예외 클래스와 상속 (Exception Class and Inheritance)

예외 클래스

  • 예외 상황을 처리할 때 필요한 변수나 함수를 멤버로 가진 클래스

  • 예외 클래스를 사용하지 않은 예제

    #include <iostream>
    
    using namespace std;
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw -1;
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (const int& x)
      {
        cerr << "Exception " << x << endl;
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception -1
    */

  • 예외 클래스를 직접 만들어서 사용하기

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw Exception();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */

예외 클래스를 상속해서 사용하기

  • 다형성을 적용하지 않는 경우

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */
    • 객체 잘림으로 인해 Exception::report() 함수가 작동한 것을 알 수 있다.
  • 다형성을 적용한 경우 (virtual 키워드 사용)

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      virtual void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Array exception report
    */

  • 맨 처음 catch된 부분만 처리된다.

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
      catch (ArrayException& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */
    warning C4286: 'ArrayException &': is caught by base class ('Exception &') on line 44
    • 따로 처리해야 하는 경우에는 더 작은 범위의 클래스를 위에 두면 된다.

      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (ArrayException& e)
        {
          e.report();
        }
        catch (Exception& e)
        {
          e.report();
        }
      }
      
      int            main()
      {
        doSomething();
      }
      
      /* stdout stderr
      Array exception report
      */

rethrow

  • catch문 내부에서 throw를 한 번 더 사용할 경우, 값을 전달할 때 차이가 존재한다.

    • throw e; 형태로 직접 값을 넘겨줄 경우

      • 객체 잘림이 발생했을 경우 잘린 상태 그대로 넘어간다.
      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (Exception& e)
        {
          cout << "doSomething ";
          e.report();
          throw e;
        }
      }
      
      int            main()
      {
        try
        {
          doSomething();
        }
        catch (ArrayException& e)
        {
          cout << "Main ";
          e.report();
        }
        catch (Exception& e)
        {
          cout << "Main ";
          e.report();
        }
      }
      
      /* stdout stderr
      doSomething Exception report
      Main Exception report
      */
    • throw; 형태로 값 없이 키워드만 작성할 경우

      • catch한 값을 그대로 다시 throw해서 객체 잘림이 발생하지 않는다.
      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (Exception& e)
        {
          cout << "doSomething ";
          e.report();
          throw;
        }
      }
      
      int            main()
      {
        try
        {
          doSomething();
        }
        catch (ArrayException& e)
        {
          cout << "Main ";
          e.report();
        }
        catch (Exception& e)
        {
          cout << "Main ";
          e.report();
        }
      }
      
      /* stdout stderr
      doSomething Exception report
      Main Array exception report
      */

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

C++ 예외 처리의 위험성과 단점  (0) 2021.03.22
C++ 함수 try (Function try)  (0) 2021.03.22
C++ 스택 되감기 (Stack Unwinding)  (0) 2021.03.22
C++ 예외 처리의 기본  (0) 2021.03.22
C++ 예외 처리 (Exception Handling)  (0) 2021.03.22
C++/Exception 2021. 3. 22. 00:10

스택 되감기 (Stack Unwinding)

  • throw를 통해 던질 경우, 타입에 맞는 catch를 만날 때까지 스택을 되감는다.

    • throw 뒤의 코드는 실행하지 않는다.

    • 마찬가지로 catch를 만나지 못해서 스택을 되감는 경우에도 뒤의 코드를 실행하지 않고 바로 리턴한다.

    • 아래 코드를 디버깅하여 한 줄씩 보면 이해하기 편하다.

    #include <iostream>
    
    using namespace std;
    
    void        last()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw - 1;
    
      cout << "End Last\n";
    }
    
    void        third()
    {
      cout << "Third function\n";
    
      last();
    
      cout << "End Third\n";
    }
    
    void        second()
    {
      cout << "Second function\n";
    
      try
      {
        third();
      }
      catch (double)
      {
        cerr << "Second caught int exception\n";
      }
    
      cout << "End Second\n";
    }
    
    void        first()
    {
      cout << "First function\n";
    
      try
      {
        second();
      }
      catch (int)
      {
        cerr << "First caught int exception\n";
      }
    
      cout << "End First\n";
    }
    
    int            main()
    {
      cout << "Main Start\n";
    
      try
      {
        first();
      }
      catch (int)
      {
        cerr << "Main caught int exception\n";
      }
    
      cout << "End Main\n";
    }
    
    /* stdout stderr
    Main Start
    First function
    Second function
    Third function
    Last function
    Throw exception
    First caught int exception
    End First
    End Main
    */

Ellipses

  • 생략 부호(...)를 사용하여 명시하지 않은 예외들에 대한 처리를 할 수 있다.

    #include <iostream>
    
    using namespace std;
    
    void        last()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
    
    void        third()
    {
      cout << "Third function\n";
    
      last();
    
      cout << "End Third\n";
    }
    
    void        second()
    {
      cout << "Second function\n";
    
      try
      {
        third();
      }
      catch (double)
      {
        cerr << "Second caught int exception\n";
      }
    
      cout << "End Second\n";
    }
    
    void        first()
    {
      cout << "First function\n";
    
      try
      {
        second();
      }
      catch (int)
      {
        cerr << "First caught int exception\n";
      }
    
      cout << "End First\n";
    }
    
    int            main()
    {
      cout << "Main Start\n";
    
      try
      {
        first();
      }
      catch (int)
      {
        cerr << "Main caught int exception\n";
      }
      catch (...)
      {
        cerr << "Main caught ellipses exception\n";
      }
    
      cout << "End Main\n";
    }
    
    /* stdout stderr
    Main Start
    First function
    Second function
    Third function
    Last function
    Throw exception
    Main caught ellipses exception
    End Main
    */

Exceiption Specifier

  • 함수를 정의할 때 body 전에 throw(자료형)의 형식으로 작성하여 예외를 던질 가능성이 있는 함수임을 명시할 수 있다.

    void        last() throw(...)
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
  • throw() 처럼 파라미터를 작성하지 않을 경우에는 "예외를 던지지 않는다" 라는 뜻으로 사용된다.

    void        last() throw()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
    warning C4297: 'last': function assumed not to throw an exception but does
C++/Exception 2021. 3. 22. 00:10

예외 처리의 기본

try, throw, catch

  • try : 코드를 실행할 부분

  • throw : 예외 상황이 발생하면 해당 정보를 던지는 부분

  • catch : try 내부에서 throw가 발생했을 경우 이를 받아주는 부분

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      double    x;
      cin >> x;
    
      try
      {
        if (x < 0.0)
          throw string("Negative input");
        cout << std::sqrt(x) << endl;
      }
      catch (string error_message)
      {
        cout << error_message << endl;
      }
    }
    
    /* stdin
    -10
    */
    
    /* stdout
    Negative input
    */

  • 묵시적 형변환이 허용되지 않는다.

    ex) std::string -> const char* 불가능

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      double    x;
      cin >> x;
    
      try
      {
        if (x < 0.0)
          throw "Negative input";  // 런타임 에러
        cout << std::sqrt(x) << endl;
      }
      catch (string error_message)
      {
        cout << error_message << endl;
      }
    }

주의사항

  • 연산량이 많아서 처리속도가 느려지므로, 성능이 중요한 프로그램에서는 가급적 사용을 지양해야 한다.

    • 조건문으로 거를 수 있는 예외 상황은 조건문을 사용해서 처리하는게 좋다.

    • 예측 불가능한 예외 사항에 대해서만 사용하는게 좋다.

C++/Exception 2021. 3. 22. 00:09

예외 처리 (Exception Handling)

예외 처리의 기본

스택 되감기 (Stack Unwinding)

예외 클래스와 상속 (Exception Class and Inheritance)

std::exception

함수 try (Function try)

예외 처리의 위험성과 단점