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

Jangmanbo·2023년 2월 21일
0

Effective C++

목록 보기
8/33
class DBConnection {
public:
	...
    static DBConnection create();	// DBConnection 객체를 반환하는 함수 (편의상 매개변수 생략)
    
    void close();	// DB 연결 닫기. 이때 연결이 실패하면 예외를 던짐
}

사용자가 직접 DBConnection 객체에 대해 close를 호출해야 하는 코드이다.
그러나 사용자가 close를 호출하는 것을 잊을 수도 있다.
이를 방지하기 위해 DBConnection에 대한 자원 관리 클래스(DBConn)를 만들고, 그 클래스의 소멸자에서 close를 호출하게 만든다.

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

DBConn 클래스를 정의함으로써 DBConn 객체가 소멸될 때 DBConnection 객체에 대한 close 함수가 자동으로 호출된다.
문제는 만약 close에서 예외가 발생하면 DBConn은 이 예외를 소멸자 밖으로 전파할거라는 것이다.


일반적인 해결법

1. close에서 예외 발생 시 프로그램을 바로 종료한다. (대개 abort 호출)

DBConn::~DBConn()
{
	try { db.close(); }
    catch (...) {
    	// close 호출이 실패했다는 로그
        std::abort();
    }
}

객체 소멸 중에 에러가 발생한 후 프로그램을 계속 실행할 수 없는 상황에서 괜찮은 선택이다.

2. close를 호출한 곳에서 일어난 예외를 무시한다.

DBConn::~DBConn()
{
	try { db.close(); }
    catch (...) {
    	// close 호출이 실패했다는 로그
    }
}

무엇이 잘못됐는지에 대한 중요한 정보가 묻혀버리기 때문에 좋은 선택은 아니다.
하지만 프로그램이 신뢰성 있게 실행을 지속할 수 있다면, 프로그램 종료나 미정의 동작으로 인해 입는 위험 감수보다는 예외를 무시하는 게 나을 수도 있다.


더 좋은 해결법

위에서 언급한 두 선택지 모두 문제가 있으므로, DBConn 인터페이스를 잘 설계해서 문제에 대처할 기회를 사용자가 가질 수 있도록 만들어보자.

class DBConn {
public:
	...
    void close()
    {
    	db.close();
        closed = true;
    }
    
    ~DBConn()
    {
    	if (!closed)
    	{
        	try { db.close(); }		// 사용자가 db 연결을 닫지 않았으면 닫기
            catch (...) {						// db 연결 닫기에 실패하면, 실패를 알린 후
            	// close 호출이 실패했다는 로그
                ...								// 프로그램 종료 or 예외 무시
            }
        }
    	db.close();
    }
    
private:
	DBConnection db;
};

DBConn에서 close 함수를 직접 제공하여 사용자가 db.close 실행 중에 발생하는 예외를 직접 처리할 수 있도록 한다. 이 기회를 사용자가 무시하더라도 DBConn의 소멸자에서 close를 호출해 마무리하겠지만, 소멸자에서 호출하는 close도 실패해 예외가 발생한다면 아까와 동일한 상황이 발생할 것이다.
중요한 것은 앞서 사용자에게 에러를 처리할 수 있는 기회를 줬다는 것이다.

또한 DBConnection이 닫혔는지 여부를 가지고, 닫히지 않았을 경우 DBConn의 소멸자에서 닫을 수도 있어 db 연결이 누출되지 않는다.


즉 위의 코드는 close 호출의 책임을 DBConn의 소멸자가 아닌 DBConn의 사용자에게 넘기는 코드이다.

  1. 어떤 동작이 예외를 일으키면서 실패할 가능성이 있고,
  2. 그 예외를 처리해야 할 필요가 있다면
    => 그 예외는 소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다.

예외를 일으키는 소멸자는 프로그램의 불완전 종료나 미정의 동작의 위험을 내포하기 때문이다.


정리

  • 소멸자 안에서 호출하는 함수가 예외를 던질 가능성이 있다면, 프로그램을 종료하거나 예외를 무시해야 한다.
  • 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 함수는 반드시 보통(소멸자가 아닌) 함수이어야 한다.

0개의 댓글