항목 8 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
솔직히 개인적으로 이해하기에 머리가 아팠던 부분이다.
우선 아래 코드를 좀 보자.
class DBConnection
{
public:
...
static DBConncection create();
void close(); // 데이터 베이스와의 연결을 닫음, 이때 실패시 예외를 던짐.
};
위와 같이 데이터 베이스와 연결을 하고 연결을 종료하는 것을 관리해주는 DBConnection이라는 클래스가 있다고 치자. 그리고 이 클래스는 아래 클래스에서 다음과 같이 사용되고 있다.
class DBConn
{
public:
...
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
자 DBConn이라는 클래스는 DBConnection이라는 클래스 객체의 연결과 종료를 자동으로 해주는 역할을 한다. 이를 위해서 소멸자에서 DBConnection객체의 close함수를 호출 시켜준다. 그런데 만약 데이터 베이스와의 연결을 끊는 도중 예외가 발생했다고 치자. 정상적으로 종료 되었다면 문제가 안되었겠지만 close함수는 예외를 던져버리게 된다. 그러면 예외는 그대로 DBConn의 소멸자 밖으로 빠져 나오게 된다. C++에서 예외가 소멸자를 벗어나는 일은 상당히 위험하다. 어떤 동작을 일으킬지 전혀 예상할 수 없기 때문이다.
자 그렇다면 이것을 어떻게 해결을 하느냔데 방법은 두 가지가 있다. 우선 첫번째 방법은 close에서 예외가 발생하면 그 예외로 프로그램에 무슨 불상사가 일어날지 모르니 프로그램을 바로 종료 시켜 버리는 방법이다.
DBConn::~DBConn()
{
try {db.close();}
catch (...) {
// close 호출이 실패했다는 로그 작성
std::abort(); // 프로그램 즉시 종료
}
}
다음으로 두 번째 방법은 그냥 예외를 삼켜 버리는 방법이다. 단순히 예외가 발생했으니 참고하라는 실패로그만 작성하고 프로그램을 종료시키지는 않는다.
DBConn::~DBConn()
{
try {db.close();}
catch (...) {
// close 호출이 실패했다는 로그 작성
}
}
하지만 이런식으로 예외를 삼켜버리는 것은 예외를 삼켜 버려도 프로그램이 정상 동작 할 수 있어야만 적절한 선택이 된다. 그래서 첫 번째 방법을 쓰자니 프로그램을 무작정 종료하는것도 그렇게 좋지는 않은것 같다. 사실 어떤 방법을 선택해도 최종적으로는 이 두 방법중 하나에 도달을 해야한다. 그래서 우리가 할 수 있는 최선은 이 방법에 도달하기 전에 사용자에게 문제를 대처할 기회를 제공하자는 것이다. 그래서 우리는 다음과 같이 코드를 작성 할 수 있다.
class DBConn
{
public:
...
void close() // 사용자가 직접 호출을 할 수 있도록 만듬.
{
db.close();
closed = true;
}
~DBConn()
{
if (!closed)
{
try {
db.close();
} catch (...) {
// close 호출이 실패했다는 로그 작성
// 이후 아까 두 방법중 하나 실행
}
}
private:
DBConnection db;
bool closed;
};
자 이렇게 하면 사용자가 직접 데이터베이스 연결을 끊을 수 있는 기회가 생긴다. 그러면 설사 오류가 발생했다 하더라도 일반 함수에서 발생한것 이므로 사용자가 적절한 처리를 해줄 수 있게된다. 물론 이렇게 해서도 처리가 안된다면 최종적으로는 아까 두 방법중 하나를 실행하겠지만 그래도 사용자가 처리 할 수 있는 자유도를 제공함으로써 더 나은 설계가 되는 것이다. 이번 내용에서는 가장 중요한것은 무슨 일이 있어도 오류는 소멸자를 벗어나게 하지 말자는 내용이다. 차라리 프로그램을 강제 종료하든지 아예 삼켜버리든지 해야지 절대로 소멸자 밖으로 나오게 해서는 안된다. 이를 명심하자!