Effective/Effective C++

[EC++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자

seeyoun 2024. 11. 16. 22:22

소멸자에서 예외를 던진다면?

  • 소멸자에서 예외를 던진다면 사용자는 예외에 대처할 기회가 없어진다.
  • 소멸자는 자원 해제에 많이 사용하므로, 예외를 던지면 자원 해제가 완료되지 않았을 위험이 크다.
  • C++에서는 예외 중첩(이미 처리 중인 예외 + 새로운 예외)이 발생하면 프로그램이 강제종료된다.

 


소멸자에서 예외를 던질때의 예시

  • 크기가 10인 클래스 A타입의 벡터의 소멸자를 호출하는 경우
    • 만약 첫번째 원소의 소멸자에서 예외가 발생한다면, 예외를 전파할 것이다.
    • 두번째 원소의 소멸자에서도 예외가 발생한다면, 이미 첫번째 예외를 처리중이므로 예외가 중첩된다.
    • C++ 표준에서는 예외가 중첩되면 std::terminate()를 호출하여 프로그램이 강제로 종료된다.
  • DBConnection 클래스의 소멸자에서 db.close()를 호출하는 경우
    • 소멸자에서 예외가 발생한다면 예외가 전파되어 소멸자에서 예외가 나가도록 내버려둔다.

 


해결 방안

  • close 예외가 발생하면 프로그램을 즉시 끝낸다.
    • try catch문을 사용하여, catch문에 넘어오면 실패 로그를 띄우고 abort를 호출한다.
    • 에러 발생 후에 프로그램 지속이 어려운 경우에 이 방식을 선택한다.
  • close를 호출한 곳에서 예외를 삼켜버린다.
    • try catch 문에서 예외 로그만 띄우고 넘어가는 것을 의미한다.
    • 대부분의 경우에서 좋은 발상은 아니나, 경우에 따라서는 불완전한 프로그램 종료 혹은 미정의 동작으로 인해 입는 위험을 감수하는 것보다 그냥 예외를 먹어버리는 것이 나을 수도 있다.
    • 단, 예외를 그냥 무시한 뒤라도 프로그램이 신뢰성 있게 실행을 지속할 수 있어야 한다.
  • close 호출 책임을 소멸자에서 사용자에게로 넘긴다.
    • DBConnection이 닫혔는지 여부를 유지하다가, 닫히지 않았으면 소멸자에서 닫을 수 있도록 하는 것이다.
    • 물론 소멸자에서까지 실패하면 위의 두 방법으로 돌아온다.

 


어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 (소멸자가 아닌) 보통의 함수여야 한다.

  • 사용자에게 에러를 처리할 수 있는 기회를 주는 것이다.

 


추가

+) 예외 처리 유형

  • throw: 예외를 발생시켜 호출자에게 알림.
    • throw 키워드 뒤에 전달된 값을 예외 객체로 생성하고 위로 던진다. 이는 catch 블록에서 사용할 수 있다.
    • throw; 를 사용하면 동일한 예외를 다시 위로 던질 수 있다. 
  • try-catch: 예외가 발생할 가능성이 있는 코드와 그 예외를 처리하는 코드.
    • try: 예외가 발생할 가능성이 있는 코드를 감싼다.
    • catch: throw된 예외를 받아서 처리한다.
  • nonexcept: 해당 함수가 절대 예외를 던지지 않음을 명시하는 키워드

 

 +) 예외가 발생하면 일어나는 일

  • 현재 실행 흐름 중단
    • 현재 함수의 실행이 즉시 중단되며, 이후 코드는 실행되지 않는다.
  • 예외 객체 전달
    • throw 키워드 뒤에 전달된 값을 예외 객체로 생성하고 위로 던진다. 이는 catch 블록에서 사용할 수 있다.
    • throw; 를 사용하면 동일한 예외를 다시 위로 던질 수 있다. 
  • 스택 해제
    • 예외가 발생한 함수에서 호출 스택을 거슬러 올라가며, 해당 함수를 호출한 상위 함수로 예외를 전달한다.
      • 이 과정에서, 예외를 처리하지 않는 함수는 스택에서 제거되며, 지역 변수의 소멸자가 호출된다.
      • 예외가 발생한 함수나 호출 스택 상위에 catch 블록이 존재한다면, 예외를 잡아서 처리한다.
  • 예외를 처리하지 못한다면
    • 예외를 처리할 catch 블록을 찾지 못하면, std::termincate()를 호출하여 프로그램을 강제종료한다.