[Effective C++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자

C++/Effective C++

2020. 7. 27. 22:32

1. C++은 소멸자에서 나오는 예외를 싫어한다

class Widget{
public:
...
~Widget(){...}
};

void doSomething()
{
	std::vector<Widget> v;
    	...
};

vector v가 소멸되는 경우에 차례대로 요소의 소멸자가 호출된다. 첫 번째 요소에서 예외가 발생한다 하더라도 나머지 요소들 소멸자를 호출하기 위해 두 번째 요소의 소멸자를 호출하게 된다. 이 때 예외가 또 발생한다면 C++ 입장에서 감당하기 버거우며 미정의 동작 가능성을 내포하게 된다.

 

2. 데이터베이스 연결을 통해 예외 파악하기

class DBConnection{
public:
//...
	static DBConnection Create();
    	void Close();
};

class DBConn{
public :
	~DBConn()
    {
    	db.close();
    }
private :
	DBConnection db;
};

// ...
{
	DBConn dbc(DBConnection::Create());
}

문제는 dbc가 블록을 빠져 나간 경우 그 소멸자에서 예외가 나가도록 내버려두는 점이다. 예외를 던지는 소멸자는 곧 '걱정거리'를 의미한다.

 

3. 걱정거리를 피하는 방법

  • close에서 예외가 발생하면 프로그램을 바로 끝낸다.
  • close를 호출한 곳에서 일어난 예외를 삼켜 버린다.

둘 다 좋은 방법은 아니다. 예외를 던진 이후에 사용자가 조치를 취할 방법이 없기 때문이다.

 

class DBConn{
public:
// ...
	void Close()
    {
    	db.close();
        closed =true;
    }
    
    ~DBConn()
    {
    	if(!closed)
        try{
        	db.close();
        }
        catch(...){
        // close 호출 실패 로그 작성
        }
     }
private:
	DBConnection db;
    	bool closed;
};

이 코드에서 중요한 점은 그 예외는 소멸자가 아닌 다른 함수에서 비롯된 것이라는 점이다. 예외를 일으키는 소멸자는 시한포탄이나 마찬가지라서 프로그램의 불완전 종료 혹은 미정의 동작의 위험을 내포하고 있기 때문이다.

 

위 코드에서는 사용자에게 예외를 대처할 기회를 한 번 더 얻게된다. (Close()에서 에러가 터져도 소멸자에서 한 번 더 닫아준다.)

 

이것만은 잊지 말자!

  • 소멸자에서는 예외가 빠져나가면 안됩니다. 만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면, 어떤 예외이든지 소멸자에서 모두 받아낸 후에 삼켜버리든지 프로그램을 끝내든지 해야 합니다.
  • 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수(즉, 소멸자가 아닌 함수)이어야 합니다.