본문 바로가기

Effective C++ 정리

08. 소멸자와 예외 처리

소멸자에서 예외 처리를 해야 하는 상황

결론부터 봤을 때, 소멸자에서 예외를 던지는 것은 좋지 않다고 합니다.

 

class DBConnection
{
  public:
    ...
  
  static DBConnection create(); // 자신의 객체를 반환. 매개변수 생략
  
  void close(); // 연결을 닫고, 실패하면 예외를 던지는 코드가 있다고 가정
};

// DBConnection 객체를 관리하는 클래스
class DBConn
{
  pubilc:
    ...
    ~DBConn()
    {
         m_db.close(); // 소멸자를 통해 연결이 항상 닫히도록 한다.
    }
    
  private:
  	class DBConnection m_db;
};

 

{
    // 블록 시작
    DBConn dbc(DBConnection::create());
    
    ...
    
    // 블록 종료
    // dbc 객체 소멸자 호출(DBConnection::close 호출)
}

 

위에 있는 dbc 객체는 함수를 빠져나올 때 소멸자를 호출할 것입니다.

그래서 DBConnection의 close 함수를 호출하면서 예외를 던질 수 있습니다.

하지만 소멸자에서 예외를 던지면 미정의 동작으로 빠질 수도 있습니다.

이럴 땐 차라리 소멸자에서 프로그램을 강제로 종료하거나 예외를 삼켜버리라고 합니다.

프로그램이 그대로 미정의 동작으로 흐르는 것보다 나을 수 있기 때문입니다.

 

// 소멸자에서 프로그램 종료
// 소멸자에서 에러 발생 후 프로그램을 계속 실행할 수 없는 상황에서 괜찮은 선택이 될 수도 있다.
DBConn::~DBConn()
{
    try(m_db.close())
    {
    	...
    }
    
    catch(...)
    {
    	close 호출 실패 로그 출력
        
        // 프로그램 종료
        // 예외를 밖으로 던지지 않고 미정의 동작이 발생할 수 있는 것을 미리 막는다.
        std::abort();
    }
};

 

// 소멸자에서 예외 삼켜버리기
// 어떻게든 소멸자에서 예외를 삼켜버린다.
// 그러나 이렇게 할 경우 무엇이 잘못됐는지 정확한 정보가 없기 때문에 좋은 방법은 아니다.
// 예외 삼키기는 예외를 무시한 뒤라도 프로그램의 실행이 지속되어야한다는 신뢰성이 있어야 한다.
CDBConn::~CDBConn
{
    try(m_db.close())
    {
    	...
    }
    
    catch(...)
    {
    	close 호출 실패 로그 출력
    }
};

 

close 함수가 최초로 예외를 던질 때, 이에 대한 대책이 없어서

어느쪽을 택하든 좋은 쪽이 없어보입니다.

 

단지, 두 방법의 공통적인 의도는 어떻게든 소멸자 내에서

예외가 빠지나가지 않도록 막는 것입니다.

 

예외가 발생할 수 있는 함수는 따로 정의한다

예외는 소멸자가 아닌, 프로그램이 실행중인 함수에서 비롯되어야 합니다.

그래서 예외가 발생할 수 있는 함수를 사용자가 미리 정의해둡니다.

 

class DBConn
{
  pubilc:
    // 예외를 던질 수 있는 함수(db.close)를
    // 사용자가 직접 호출할 수 있도록 정의한다.
    void close()
    {
	   db.close();
	   closed = true;
    }
  
  
    DBConn()   :
       m_bIsClose(false)
    {...}
    
    ~DBConn()
    {
         // 사용자가 연결을 닫지 않았으면 소멸자에서 시도해본다.
         // 실패하면 실행을 끝내거나 예외를 삼킨다.
         if(!m_bIsClose)
         {
            try{m_db.close()}
            
            catch(...)
            {
               close 호출 실패 로그 출력
            }
         }
    }
    
  private:
     class DBConnection m_db;
     bool m_bIsClose;
};

 

이제 close 함수를 소멸자가 아닌 사용자가 원하는 시점에 처리하면

예외를 능동적으로 처리할 수 있게 됩니다.

소멸자에서 프로그램을 종료시키거나 예외를 삼켜버리는 방법은 최후의 보루로 남겨둡니다.