스토리텔링 개발자

[More Effective C++] 14. 예외 지정(예외 명세) 본문

개발/More Effective C++

[More Effective C++] 14. 예외 지정(예외 명세)

김디트 2024. 8. 19. 13:15
728x90

항목 14. 예외 지정(exception specification) 기능은 냉철하게 사용하자

 

 

 

예외 지정(exception specification)
  • 함수를 선언할 때 함수가 발생시킬 예외를 미리 지정하는 기능
  • 장점
    1. 어떤 함수가 어떤 예외를 발생시키는지가 드러나기에 코드 가독성이 좋아진다.
    2. 예외 지정에 일관성이 없으면 컴파일러가 컴파일 중 발견해준다.
    3. 함수가 예외 지정 리스트에 없는 예외를 발생시킬 경우
      • 런타임 에러가 발생하면서 unexpected라는 특수 함수가 자동으로 호출된다.
  • 단점
    1. unexpected 함수의 기본 동작은 std::terminate를 호출하는 것이다.
      • terminate는 기본적으로 abort를 호출하는데, 이는 프로그램을 바로 멈춰버린다.
      • 활성 스택 프레임에 만들어진 지역 변수는 소멸되지 않는다.
    2. 컴파일러가 해주는 예외 지정 일관성 점검이 극히 부분적이다.
      • 예외 지정 함수 내에서 예외 지정을 어기는 함수를 호출하는 부분까지는 점검해 주지 않는다.
        • C++ 표준안에 의하면, 경고는 내더라도 거부는 하면 안되도록 되어 있다.
      • extern void f1(); // 예외 지정이 없는 함수
        void f2() throw(int); // int 타입 예외만을 발생시키는 함수
        
        void f2() throw(int)
        {
            ...
            f1(); // f1은 int 이외 타입의 예외를 발생시킬 수 있다.
            // 하지만 컴파일러는 별다른 말을 하지 않는다.
            // 즉, 문법은 맞다.
            ...
        }
    3. 템플릿에는 사용이 불가능한 것과 마찬가지이다. (아래에서 자세히 설명)
    4. 예외 처리 코드가 준비된 상황에서조차 unexpected가 호출될 수 있다.
      • class Session
        {
        public:
            ~Session();
            ...
        private
            static void logDestruction(Session* objAddr) throw(); // 예외가 발생하지 않도록 예외지정
        };
        
        Session::~Session()
        {
            try
            {
                logDestruction(this);
                // 허나.. logDestruction 내부의 어떤 함수에서 예외가 발생한다면?
                // unexpected가 호출되면서,
                // 외부에서 준비된 try / catch 문은 실행조차 되지 않고 프로그램이 멈춘다!!
            }
            catch(...) { } // 모든 예외를 붙잡아 처리한다.
        }
    5. 모던 C++에서는 더이상 지원하지 않는 기능이다. (C++14 이후부터)
      • 일반적인 예외 지정은 지원하지 않는다.
      • 예외 발생 금지(throw())의 경우 noexcept 키워드 사용이 권장된다.

 

 

 

예외지정 불일치 피하기

 

1. 템플릿에 예외 지정을 두지 않는다.

  • 템플릿의 타입 매개변수에서 발생된 예외에 대해 알 방법이 없다.
  • 템플릿이 받아들이는 타입 매개변수는 무궁무진하기 때문이다.
// 예외 지정에 대해 신경쓰지 않은 템플릿
// 예외가 발생하지 않도록 지정되어 있다.
// 하지만...
// 어떤 타입이 &operator를 오버로딩 했다면?
// 거기서 예외가 발생할 수도 있고, 그렇다면 unexcepted 행이다.
template<typename T>
bool operator==(const T& lhs, const T& rhs) throw()
{
    return &lhs == &rhs;
}

 

2. 예외 지정이 안된 함수를 호출할 가능성이 있는 함수에는 예외지정을 두지 않는다.

  • 하지만.. 이게 가능한 일일까?
// 콜백을 위한 함수 포인터
typedef void (*CallBackPtr) (int eventXLocation,
                             int eventYLocation,
                             void* dataToPassBack);

class CallBack
{
public:
    CallBack(CallBackPtr fPtr, void* dataToPassBack)
    : func(fPtr), data(dataToPassBack) {}
    
    void makeCallBack(int eventXLocation,
                      int eventYLocation) const throw();

private:
    CallBackPtr func; // 콜백이 일어날 때 호출되는 함수
    
    void* data; // 콜백 함수로 넘겨지는 데이터
};

// 콜백 함수 호출
// 허나, func는 예외 지정을 어길 위험을 안고 있다.
// func가 예외를 던질지 말지, 어떤 예외를 던질지 전혀 알 방법이 없다.
void CallBack::makeCallBack(int eventXLocation,
                            int eventYLocation) const throw()
{
    func(eventXLocation, eventYLocation, data);
}
typedef void (*CallBackPtr) (int eventXLocation,
                             int eventYLocation,
                             void* dataToPassBack) throw();
// CallBackPtr에 예외지정을 함으로써 해결해보자.
// 참고) 하지만... C++ 표준으로는 예외 지정은 typedef에 넣으면 안된다고 해서 여전히 문제 있음..


void* callBackData;

void callBackFcn1(int eventXLocation, int eventYLocation, void* dataToPassBack);
CallBack c1(callBackFcn1, callBackData); // 에러! 예외 지정이 없다.

void callBackFcn2(int eventXLocation, int eventYLocation, void* dataToPassBack) throw();
CallBack c2(callBackFun2, callBackData); // 성공

 

3. "시스템"이 일으킬 가능성이 있는 예외(C++ 표준 예외)를 처리하도록 한다.

  • 가장 흔한 C++ 표준 예외는 bad_alloc이다.(항목 8 참조)
  • 예기치 않은 예외의 발생을 막는 것보다 그 예외와 만나기가 더 쉽다.
    • 예외 지정을 사용하고 있는데, 사용자가 사용하게 된 라이브러리 함수가 예외 지정을 사용하지 않는다면?
  • C++에는 예기치 않은 예외를 다른 타입 예외로 대체할 방법이 있다.
    • set_unexpected 를 사용하는 것이다.
    • 사실 여기에 적용되어 있는 디폴트 함수가 바로 unexpected 함수이다.
class UnexpectedException {}; // 모든 예기지 않은 예외는 이 타입 객체로 대체한다.

void convertUnexcepted() // 예기지 않은 예외 발생 시 호출할 함수
{
    throw UnexpectedException();
}

set_unexpected(convertUnexpedted); // 예기치 않은 예외 발생 시 예외 대체!
// 이제 예외 지정 리스트에 UnexpectedException을 넣으면 된다.
  • 예기치 않은 예외를 잘 알려진 타입으로 바꾸는 방법도 가능하다.
    • 이를 통해 C++ 표준 예외로 변경이 가능하다!
void convertUnexpected()
{
    throw; // 그냥 다시 던진다.
    // 이 경우 그냥 bad_exception 객체로 바뀐다.
}

set_unexpected(convertUnexpected);
// unexpected 대신 그냥 다시 던지도록 셋팅한다.
// 이제 예외 지정 리스트에 bad_exception을 넣으면 된다.

 

 

 

참조
 

[Effective C++] 29. 예외 안전성

항목 29 : 예외 안전성이 확보되는 그날을 위해 싸우고 또 싸우자!   예외 안정성을 고려하지 않은 코드class PrettyMenu{public: ... void changeBackground(istream& imgSrc); // 배경 그림을 바꾸는 함수 ...private:

delightlane.tistory.com

 

728x90
Comments