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()를 호출하여 프로그램을 강제종료한다.