아래 코드를 보자
class Widget
{
public:
...;
~Widget() { ...; } //이 함수로부터 예외가 발생된다고 가정하자
};
void doSomething()
{
std::vector<Widget> v;
} //v는 여기서 자동으로 소멸한다.
위의 코드에서 Widget형인 vector가 10개가 있다고 치자
그런데 첫 번째 Widget을 제거하려는데 소멸자에서 예외가 터져버렸다. 근데 아직 9개나 남아있으므로 다음 것을 또 제거하려 했는데 또 소멸자에서 예외가 터져버렸다. 어떠한 조건에 의해 예외가 동시에 터져서 C++ 이 감당하기 어려워진다면 프로그램이 꺼진다거나 오작동을 보일 것이다. 다른 예시를 보자
class DBConnection
{
public:
...
static DBConnection create(); //DBConnection 객체를 반환하는
//함수. 매개변수는 편의상 생략한다.
void close(); //연결을 닫는다. 이때
//연결이 실패하면 예외를 던진다.
};
보다시피 사용자가 DBConnection객체에 대해 close를 직접호출해야 하는 설계이다. 사용자가 망각을 하지 않으면 괜찮지만 혹시 모르니 자원관리 클래스를 만들었다.
class DBConn //DBConnection객체를 관리하는
{ //클래스
private:
DBConnection db;
public:
...
~DBConn() //데이터베이스 연결이 항상 닫히도록
{ //확실히 챙겨주는 함수
db.close();
}
};
우리의 배려덕에 다음과 같은 프로그래밍이 가능해진다 아래의 코드를 보자
{ //블록 시작
DBConn dbc(DBConnection::create()); //DBConnection 객체를 생성하고
//이것을 DBConn 객체로 넘겨서
//관리를 맡깁니다.
//DBConn 인터페이스를 통해
//그 DBConnection 객체를 사용한다.
//블록 끝. DBConn 객체가
//여기서 소멸된다. 따라서
//DBConnection 객체에 대한 close
//함수의 호출이 자동으로 이루어진다.
}
하지만 좋게만 동작하면 좋겠지만 또 예외가 터진다면 아까 말했듯이 오작동을 일으킬 수도 있다. 그것을 막으려면 두 가지 방법이 있다.
소멸자 안에서 예외 발생 시 소멸자 안에서 abort를 호출해 프로그램을 꺼버리는것
DBConn::~DBConn()
{
try { db.close(); }
catch (...)
{
close 호출이 실패했다는 로그 작성;
std::abort();
}
}
그리고 소멸자 안에서 예외를 삼키는 경우이다.
DBConn::~DBConn()
{
try { db.close(); }
catch (...)
{
close 호출이 실패했다는 로그 작성;
}
}
이 경우는 별로 좋지는 않다 무엇이 잘못됐는지에 대한 정보가 묻혀버리기 때문이다. 그래도 꺼지거나 오작동으로 피해를 입는 것보다 삼키는 것이 나으면 이걸 쓴다고 한다.
하지만 예외를 던지게 된 요인에 대해 전혀 대처할 수 없다는 게 아까 말한 두 가지 방법의 공통된 단점이다.
그래서 생각해낸 것이 자원을 할당 해제하는 close함수를 사용자가 직접 사용하여 대처할 수 있게 만드는 것이다.
class DBConn
{
private:
DBConnection db;
bool closed;
public:
...;
void close() //사용자 호출을 배려(?) 해서
{ //새로 만든 함수
db.close();
closed = true;
}
~DBConn()
{
if(!closed) //사용자가 연결을 안 닫았으면
try //여기서 닫는다.
{
db.close();
}
catch (...) //연결을 닫다가 실패하면,
{ //실패를 알린 후에
close 호출이 실패했다는 로그를 작성한다.; //실행을 끝내거나 예외를 삼킨다.
....
}
}
};
예외가 발생하면 사용자가 close함수를 사용하여 예외에 대처하고 사용자가 close함수로 할당 해제를 안 했으면 소멸자에서 닫고 이것도 실패하면 뭐..... ㅋㅋ
(결국은 책임전가 ㅋㅋㅋㅋ)
그래도 결론은 "사용자에게도 대처할 기회를 주자"라는 것 같다.
이것만은 잊지 말자!
1. 소멸자에서는 예외가 빠져나가면 안 된다. 만약 소멸자 안에서 호출된 함수가 예외를 던진 가능성이 있다면, 어떤 예외이든지 소멸자에서 모두 받아낸 후에 삼켜버리든지 프로그램을 끝내든지 해야 한다.
2. 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수(즉, 소멸자가 아닌 함수)이어야 한다.