C++에서 예외 처리는 스택 언와인딩 메커니즘을 동반하며, 이는 예외 발생 이전에 호출 스택에 생성된 지역 객체들의 자동 파괴(소멸자 호출)를 포함합니다. 이 현상은 자원의 올바른 해제를 위해 중요합니다.
C++ 설계 초기 단계에서 내장 쓰레기 수집기가 부족하여 자원의 해제(메모리, 파일, 소켓 등)는 프로그래머에게 맡겨졌습니다. 예외 처리는 오류로 인해 코드가 롤백될 때 올바른 해제를 보장해야 했습니다.
예외 발생 시 객체 자동 파괴 메커니즘이 없다면 자원 누수가 발생합니다. 각 catch 블록에서 수동으로 자원을 해제하지 않으면 코드가 복잡하고 신뢰할 수 없게 됩니다.
C++는 예외를 던질 때 호출 스택에서 throw 이전의 영역에 있는 모든 객체가 생성된 반대 순서로 파괴되는 스택 언와인딩을 구현합니다. 이들의 소멸자는 자동으로 호출됩니다.
자원을 지속적으로 해제하는 고전적인 방법은 RAII(자원 획득은 초기화다) 패턴을 사용하는 것입니다: 모든 자원은 객체에 감싸져 있으며, 이 객체가 파괴될 때 소유한 자원을 해제합니다.
코드 예시:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("파일을 열 수 없습니다"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // throw가 발생하면 롤백됩니다. // ... 파일 작업 throw std::runtime_error("일부 오류"); // fclose는 자동으로 호출됩니다. }
주요 특징들:
단순히 try-catch를 사용하고 catch 블록에서 delete나 fclose를 수동으로 호출해서 누수를 방지할 수 없나요?
답변:
이것은 불편하고 신뢰할 수 없습니다: 여러 출구 지점이나 중첩 자원이 있을 때 자원을 닫는 것을 잊기 쉽습니다. 소멸자는 함수에서 예외가 "통과"되더라도 호출됩니다. catch는 필요하지 않습니다.
스택 언와인딩 중에 힙(heap)에 있는 객체가 파괴될까요? 이 객체들이 스택의 객체에 "감싸지지" 않는다면요?
답변:
아니요. 스택 언와인딩은 스택에 위치한 객체에 대해서만 소멸자를 호출합니다. 힙 객체가 올바르게 파괴되려면 스택의 객체가 이를 소유해야 합니다(예: 스마트 포인터를 통해).
스택 언와인딩 중 소멸자가 또 다른 예외를 던지면 어떻게 되나요?
답변:
스택 언와인딩 중 어떤 객체가 파괴되는 과정에서 두 번째 예외가 발생하면 프로그램은 std::terminate()를 호출하며 종료됩니다. 소멸자에서 예외를 던지지 마십시오!
개발자가 RAII를 사용하지 않고 catch 블록을 통해 파일을 수동으로 닫고 메모리를 해제합니다.
장점:
단점:
파일과 자원이 RAII 래퍼 클래스로 감싸져 있습니다. 결과: 해제는 항상 소멸자에서 발생하며, throw/catch와 관계없이 이루어집니다.
장점:
단점: