[Effective C++ 정리 #8] 소멸자에서 예외 처리 시 발생할 수 있는 문제 및 해결 방안
이 글은 『Effective C++』를 읽고 개인적으로 공부한 내용을 정리한 기록입니다.
저는 컴퓨터공학을 전공하지 않았으며, 프로그래밍을 공부하는 과정에서의 이해와 생각을 정리하기 위해 글을 작성하고 있습니다.
따라서 내용 중 일부에 오류나 부정확한 설명이 있을 수 있으며, 피드백은 언제든지 환영합니다. 확인 후 수정하도록 하겠습니다.
전문적인 해설이 아닌 개인적 시선에서의 정리임을 참고하고 읽어주시면 감사하겠습니다.
예외 처리(exception)이 소멸자가 끝나는 것을 막는다!
C++에서는 소멸자에서 예외를 던지는 것이 허용되지만, 매우 위험한 행위로 간주됩니다. 이번 아이템에서는 소멸자에서 예외가 발생할 경우 무슨 일이 벌어지는지와 그에 따른 대처 방법에 대해 살펴봅니다.
문제 상황: 소멸자에서 예외가 발생할 때
다음 예제를 살펴보세요:
class Widget {
public:
~Widget() {
// 예외를 던질 가능성이 있는 코드
...
throw std::runtime_error("Something went wrong");
}
};
void doSomething() {
std::vector<Widget> v(10);
} // v가 스코프에서 벗어나며 Widget 10개가 파괴됨
doSomething()
함수에서 v
라는 vector가 스코프를 벗어나면, 10개의 Widget
객체가 순차적으로 파괴됩니다. 그런데 첫 번째 Widget의 소멸자가 예외를 던지면? 나머지 9개의 소멸자는 여전히 호출되어야 합니다.
만약 두 번째 Widget도 예외를 던진다면? C++은 동시에 하나의 예외만 처리할 수 있습니다. 즉, 두 개 이상의 예외가 동시에 활성화되면 undefined behavior가 발생하고, 보통 프로그램이 강제로 종료됩니다.
컨테이너 안의 객체가 아니더라도, **단일 소멸자 안에서 예외가 발생하는 것 자체**가 전체 시스템의 불안정성을 초래할 수 있습니다.
실전 사례: DB 연결 자원 정리
다음은 DBConnection
객체를 관리하는 래퍼 클래스 DBConn
의 예시입니다.
class DBConnection {
public:
static DBConnection create();
void close(); // 실패 시 예외 발생 가능
};
class DBConn {
public:
DBConn(DBConnection dbc) : db(dbc), closed(false) {}
void close() {
db.close();
closed = true;
}
~DBConn() {
if (!closed) {
try {
db.close();
} catch (...) {
log("DB close failed in destructor");
std::abort(); // 또는 예외 삼킴 swallow
}
}
}
private:
DBConnection db;
bool closed;
};
이 구조는 다음의 기능을 가집니다:
close()
를 명시적으로 호출하면 정상 정리- 사용자가 호출하지 않았다면
~DBConn()
이close()
를 호출함 - 예외가 발생하면 로그하고 종료 또는 무시
이처럼 예외 발생 가능성이 있는 정리 작업은 소멸자 바깥에서 수행하도록 유도하고, 소멸자 내부에서는 예외를 처리만 하고 외부로 던지지 않는 것이 안전한 설계입니다.
다시 말해,close()
와 같은 명시적 함수로 예외 처리를 맡기고, 소멸자는 백업으로만 동작하도록 하면, 사용자가 에러를 제어할 수 있는 기회를 갖게 됩니다. 사용자가 close()
호출을 놓쳐도, 소멸자가 예외를 삼키거나 종료하도록 설계되어 있다면, 예외가 퍼져서 프로그램이 죽는 사태를 막을 수 있습니다
예외를 삼킬까? 종료할까?
예외가 발생했을 때 대응 방식은 두 가지가 있습니다:
- 프로그램 종료: 예외 발생 시
std::abort()
등으로 종료 - 예외 삼키기: 로그를 남기고 조용히 무시
각각 장단점이 있습니다. 프로그램 종료의 경우 정의되지 않은 동작을 방지하고 문제를 확실히 인지할 수 있다는 장점이 있습니다. 그러나 시스템 전체가 죽는다는 단점이 있죠. 반대로 예외 삼기기의 경우 프로그램 지속이 가능하지만, 문제를 은폐할 가능성이 있고 디버깅이 어렵다는 단점이 있습니다. 각 방법마다 장단점이 있으니 시스템 중요도, 장애 허용 수준, 로깅 정책에 따라 결정하라고 권장하고 있습니다.
핵심 요약
- 소멸자에서는 절대 예외를 발생시키지 않아야 한다.
- 예외가 발생할 수 있는 작업은
close()
와 같은 함수로 분리해 사용자에게 맡긴다.
감사합니다.