프로그래밍/C\C++

[Effective C++ 정리 #8] 소멸자에서 예외 처리 시 발생할 수 있는 문제 및 해결 방안

허구의 2025. 6. 18. 07:13
728x90

이 글은 『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() 호출을 놓쳐도, 소멸자가 예외를 삼키거나 종료하도록 설계되어 있다면, 예외가 퍼져서 프로그램이 죽는 사태를 막을 수 있습니다


예외를 삼킬까? 종료할까?

예외가 발생했을 때 대응 방식은 두 가지가 있습니다:

  1. 프로그램 종료: 예외 발생 시 std::abort() 등으로 종료
  2. 예외 삼키기: 로그를 남기고 조용히 무시

각각 장단점이 있습니다. 프로그램 종료의 경우 정의되지 않은 동작을 방지하고 문제를 확실히 인지할 수 있다는 장점이 있습니다. 그러나 시스템 전체가 죽는다는 단점이 있죠. 반대로 예외 삼기기의 경우 프로그램 지속이 가능하지만, 문제를 은폐할 가능성이 있고 디버깅이 어렵다는 단점이 있습니다. 각 방법마다 장단점이 있으니 시스템 중요도, 장애 허용 수준, 로깅 정책에 따라 결정하라고 권장하고 있습니다.


핵심 요약

  • 소멸자에서는 절대 예외를 발생시키지 않아야 한다.
  • 예외가 발생할 수 있는 작업은 close()와 같은 함수로 분리해 사용자에게 맡긴다.

감사합니다.

728x90