Item 8: 예외가 소멸자를 떠나지 못하게 하자
1. 소멸자에서 어떤 함수를 호출해서 예외를 받았을 때 해당 예외를 던지면 어떻게 되나?
시스템이 미정의 동작을 한다.
아래와 같은 클래스들이 있다고 가정하자.
class DBConnection {
public:
...
static DBConnection create(); // DBConnection 객체를 반환하는 함수라하자.
// 편의상 매개변수 생략
void close(); // DB 연결을 닫는다.
// 이 함수에서 예외가 발생한다고 가정하자.
};
class DBConn { // DBConnection 객체를 관리하는 클래스
public:
...
~DBConn() // 데이터베이스 연결이 항상 닫히도록
{ // 확실히 챙켜주는 역할을 한다.
db.colose();
}
private:
DBConnection db;
};
아래와 같이 사용이 가능하다.
{
DBConn dbc(DBConnection::create());
...
} // ~DBConn() 호출.
2. 만약 close()에서 예외를 던진다면 어떻게 처리해야 하나?
- 예외가 발생하면 프로그램을 바로 종료한다.
- close를 호출한 곳에서 일어난 예외를 삼킨다.
DBConn::~DBConn()
{
try { db.close(); }
catch(...) {
close 호출이 실패했다는 로그 작성;
std::abort();
}
}
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
close 호출이 실패했다는 로그를 작성한다;
}
}
3. 종료하는 경우의 장점은?
- 예외 발생으로 프로그램의 정상 동작이 어렵다면 종료도 좋을 듯.
- 예외를 던짐으로 인한 미정의 동작이 발생 안한다.
4. 삼키는 경우의 장점은?
- 프로그램 계속 진행가능
- 다만 해당 예외 후에도 프로그램이 신뢰성 있게 실행되어야 함.
- 두 방법이 좋은 방법이 좋은 방법인가?
별로 좋지 않다.
- 다른 방법은?
예외에 대처할 기회를 사용자에게 제공한다.
class DBConn {
public:
...
void close()
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed)
try {
db.close();
}
catch(...) {
close 호출이 실패했다는 로그 작성;
...
}
}
private:
DBConnction db;
bool closed;
};
7. 사용자에게 예외 처리를 떠넘기는 것인가?
- 아니다. 예외는 소멸자가 아닌 다른 함수에서 비롯되어야 한다"는 것이 핵심이다.
사용자에게 에러를 처리할 수 있는 기회를 주는 것이다.
사용자가 이 기회를 버린다면 어쩔 수 없다. 우리는 이미 그 기회를 줬고 해당 정보도 소멸자에서 기록하고 있다.
정리
- 소멸자에서는 예외가 빠져 나가지 않게 하자. 만약 소멸자에서 호출된 함수가 예외를 던질 가능성이 있다면 소멸자에서 모두 받아서 삼켜버리든지 프로그램을 끝내야 한다.
- 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면 해당 연산을 제공하는 함수는 반드시 보통함수(소멸자가 아닌 함수)이어야 한다.