C++ 가상 소멸자의 필요성
Scott Meyers는 그의 저서 Effective C++에서 다음과 같이 조언합니다.
클래스에 가상 함수가 하나라도 있다면 반드시 가상 소멸자(virtual destructor)를 가져야 하며, 기본 클래스로 설계되지 않았거나 다형적으로(polymorphically) 사용될 의도가 없는 클래스라면 가상 소멸자를 선언해서는 안 됩니다.
즉, 다형적 기본 클래스(polymorphic base class)에서는 소멸자를 반드시 virtual로 선언해야 합니다.
가상 소멸자가 없으면 어떤 문제가 발생할까?
그 이유를 이해하려면, 파생 클래스의 생성자를 통해 객체를 생성하고 기본 클래스 포인터로 가리키는 상황을 살펴봐야 합니다.
Base *b = new Derived(); // b 사용 delete b;
이때 Base의 소멸자가 가상으로 선언되어 있지 않다면, delete b; 구문은 정의되지 않은 동작(undefined behavior)을 유발합니다.
소멸자 호출은 일반적인 비가상(non-virtual) 함수처럼 정적으로 바인딩되기 때문입니다. 그 결과 컴파일러는 실제 객체 타입이 아닌 포인터 타입을 기준으로 Base의 소멸자만 호출하게 되고, 파생 클래스인 Derived의 소멸자는 전혀 실행되지 않습니다.
자원 누수(Resource Leak)로 이어지는 이유
Derived의 소멸자가 호출되지 않으면 다음과 같은 문제가 발생할 수 있습니다.
- 파생 클래스에서 동적으로 할당한 메모리가 해제되지 않음
- 파일 핸들, 네트워크 연결 등 파생 클래스가 관리하는 자원이 반환되지 않음
- 소멸자 내부에 정의된 정리(cleanup) 로직이 실행되지 않음
이것이 바로 자원 누수(resource leak)로 이어지는 원인입니다.
정리
클래스가 다형성을 위해 설계된 기본 클래스라면 소멸자를 virtual로 선언하는 것이 안전합니다. 반대로 기본 클래스로 사용될 계획이 없는 클래스라면 불필요한 vtable 오버헤드를 피하기 위해 가상 소멸자를 선언하지 않는 것이 좋습니다. 이 규칙 하나만 기억해도 C++에서 흔히 발생하는 심각한 메모리 버그를 예방할 수 있습니다.