8.예외가 소멸자를 떠나지 못하도록 붙들어 놓자
이 책에서 말하는 예외는 예외처리를 말하는 것 같다.
흔히 개발자가 생각하지 못한 상황에 대한 처리를 말한다. vector의 size가 10인데 10번 index반환을 요구하는 상황.
학원에서 이상묵 선생님이 항상 말했었다. 예외처리에 대한 생각을 고려해야 한다고.
예외처리에 대해 붙이고자 노력을 하고는 있다. 만약 반환값이 NULL이라면 return NULL한다던가 assert함수를 사용하긴 하는데 고민했던게 assert는 프로그램 자체가 터져버리니 왠지 사용하기 싫어했는데 반환값이 참조자인 경우
return NULL도 사용하지 못하니 골치가 아팠다.
C++는 예외를 내보내는 소멸자를 좋아하지 않는다.
class DBConnection
{
public:
static DBConnection Create(); //DBConnection 객체를 반환하는 함수
void close(); //연결을 닫는다. 실패시 예외를 던짐
};
class DBConn
{
public:
DBConn(DBConnection db);
~DBConn() //소멸자 DBConnection의 closd를 호출함으로써 연결을 닫는다.
{ db.close(); }
private:
DBConnection db;
};
int main()
{
DBConn dbc(DBConnection::Create()); //DBConnection 객체 생성하고 DBConn 객체로 넘김
} //DBCoon객체가 소멸되고 DBConnection에 대한 close함수의 호출이 될 것이다.
close 호출만 성공하면 아무 문제가 없는 코드인데, 만약 close에서 예외 상황이 발생했다면?
DBConn의 소멸자는 예외처리를 할 것이다. 소멸자에서 예외가 나가도록 내버려 둔다는 것인데 여기서 문제가 발생한다.
예외처리를 하는 방법은 두 가지가 있다.
1.close에서 예외가 발생하면 프로그램을 바로 종료한다. 보통 abort를 호출한다.
~DBConn()
{
try
{ db.close(); }
catch (...)
{
//로그 작성
std::abort();
}
}
객체 소멸이 진행되다 에러가 발생한 후 프로그램을 계속할 수 없는 상황이면 좋은 선택이다.
2.close를 호출한 곳에서 일어난 예외를 삼켜 버린다.
~DBConn() //소멸자 DBConnection의 closd를 호출함으로써 연결을 닫는다.
{
try
{ db.close(); }
catch (...)
{
//로그 작성
}
}
대부분 경우에서 예외 삼키기는 그리 좋은 선택이 아니다. 무엇이 잘못 됐는지 정보가 묻혀 버리기 때문이다.
발생한 예외를 무시하더라도 프로그램이 신뢰성 있게 실행이 될 수 있다는 전제조건이 있다면.
솔직히 내가 하수라서 그런지 얼마나 미미한 상황이기에 이런걸 쓸 일이 있을까 싶다.
둘 다 문제점만을 생각해 보면 특별히 좋아보이진 않는다.
프로그램이 터지냐 vs 전적으로 내 실력을 믿느냐의 선택이라 생각한다.
만약 DBConn 인터페이스 설계를 잘해서 발생할 소지가 있는 문제에 대처할 기회가 사용자에게 있다고 가정해보자.
CBConn에서 close함수를 직접 제공하게 된다면 이 함수의 실행 중에 발생하는 예외를 사용자가 직접 처리할 수 있을 것이다. 만약 DBConnection이 닫혔는지의 여부를 bool 같은 것으로 갖고 있고 닫히지 않았다면 DBConn의 소멸자에서 닫게 할 수 있을 것이다.
class DBConn
{
public:
DBConn(DBConnection db);
~DBConn() //소멸자 DBConnection의 closd를 호출함으로써 연결을 닫는다.
{
if (closed == false)
{
try
{
db.close();
}
catch (...) //터트리거나 삼키거나
{
//로그 작성
}
}
}
void close()
{
db.close();
closed = true;
}
private:
DBConnection db;
bool closed = false; //닫혔는지에 대한 여부 저장
};
하지만 이 방법 역시 소멸자에서 호출하는 close에서 예외가 발생하면 터트리거나 삼키거나 선택의 반복이 될것이다.
어떤 동작이 예외를 일으키면서 실패할 가능성이 있고 그 예외 처리를 해야 할 필요가 있다면, 그 예외 처리는 소멸자가 아닌 다른 함수에서 처리해야 한다.
예외를 일으키는 소멸자는 프로그램의 불완전 종료 혹은 예상치 못한 동작의 위험을 내포하고 있다.
위의 코드는 사용자 DBConn이 close함수를 호출 할 수 있게 했지만, 단순히 기회를 줄 뿐이다. 이마저도 없다면 사용자는 예외에 대처할 기회를 못 잡게 된다.
이번 챕터를 보고 내가 이해한 것을 정리해보자면 어지간하면 소멸자에서 예외 처리 안하게끔 하란 소리같다.
사용자에게 니가 소멸자 호출하기 전에 예외처리 미리 다하라고 짬때리라는 느낌이다.
이것만은 잊지 말자
소멸자에서 예외가 빠져나가면 안된다.
만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면 어떤 예외든지 소멸자에서 모두 받아낸 후에 삼켜 버리든지 프로그램을 터트리든지 해야한다.
어떤 클래스의 연산이 진행되다가 던전 예외에 대해 사용자가 반응해야할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 소멸자가 아닌 함수여야 한다