가상 소멸자(Virtual Destructor)는 C++의 다형성(Polymorphism) 컨텍스트에서 매우 중요한 개념이다.
부모 클래스의 포인터나 참조를 통해 자식 클래스 객체를 안전하게 삭제하기 위해 반드시 필요하다.
가상 소멸자가 필요한 이유
클래스 상속 관계에서 부모 클래스의 포인터로 자식 클래스의 객체를 가리키는 경우가 많다. (Shape* shape = new Circle();
)
이 때 delete shape;
코드를 통해 객체를 삭제하면, 컴파일 타임에 shape
포인터의 타입인 Shape
클래스의 소멸자가 호출되도록 결정된다. 만약 Shape
의 소멸자가 virtual
키워드로 선언되어 있지 않다면, 오직 Shape
의 소멸자만 호출되고 Circle
의 소멸자는 호출되지 않는다.
이 경우, Circle
클래스가 내부적으로 할당한 리소스(메모리, 파일 핸들 등)가 있다면 해당 리소스는 해제되지 않아 메모리 누수(memory leak)나 리소스 누수(resource leak)가 발생하게 된다.
가상 소멸자의 동작
부모 클래스의 소멸자를 virtual
로 선언하면, 컴파일러는 이 소멸자 호출을 정적이 아닌 동적 바인딩(Dynamic Binding)으로 처리한다. 즉, 포인터의 실제 타입(Shape*
)이 아닌, 포인터가 실제로 가리키는 객체의 타입(Circle
)에 따라 호출할 소멸자를 런타임에 결정한다.
따라서 delete shape;
가 실행되면 다음 순서로 소멸자가 호출된다.
Circle
의 소멸자 (자식 클래스 소멸자) 호출Shape
의 소멸자 (부모 클래스 소멸자) 호출
이러한 메커니즘을 통해 자식 클래스부터 부모 클래스까지 모든 소멸자가 연쇄적으로 호출되어 객체가 안전하게 완전히 소멸되는 것을 보장한다.
규칙 : 클래스를 상속하여 다형적으로 사용할 것이라면 (즉, 부모 포인터로 자식 객체를 다룰 것이라면) 부모 클래스의 소멸자는 반드시 `virtual`로 선언해야 한다. 만약 클래스에 하나라도 가상 함수가 있다면, 가상 소멸자를 추가하는 것이 안전하다.
실습 코드
#include <iostream>
// --- 부모 클래스 ---
class Base {
public:
Base() {
std::cout << "Base Constructor" << std::endl;
}
// 소멸자를 virtual로 선언하거나, 아래의 non-virtual 소멸자를 주석 해제하여 테스트.
virtual ~Base() {
std::cout << "Base Destructor (virtual)" << std::endl;
}
// Non-virtual destructor for testing
// ~Base() {
// std::cout << "Base Destructor (non-virtual)" << std::endl;
// }
};
// --- 자식 클래스 ---
class Derived : public Base {
private:
int* _pResource; // 동적 할당된 리소스를 가리키는 포인터
public:
Derived() {
_pResource = new int(100); // 객체 생성 시 리소스 할당
std::cout << "Derived Constructor (Resource Allocated)" << std::endl;
}
~Derived() {
delete _pResource; // 객체 소멸 시 리소스 해제
_pResource = nullptr;
std::cout << "Derived Destructor (Resource Deallocated)" << std::endl;
}
};
int main() {
std::cout << "--- Creating Derived object through Base pointer ---" << std::endl;
Base* pBase = new Derived(); // 부모 포인터로 자식 객체 생성
std::cout << "--- Deleting object through Base pointer ---" << std::endl;
delete pBase;
std::cout << "--- Analysis ---" << std::endl;
std::cout << "If Base destructor is virtual:" << std::endl;
std::cout << " 1. Derived's destructor is called (Resource Deallocated)." << std::endl;
std::cout << " 2. Base's destructor is called." << std::endl;
std::cout << " => No memory leak." << std::endl;
std::cout << "If Base destructor is NOT virtual:" << std::endl;
std::cout << " 1. Only Base's destructor is called." << std::endl;
std::cout << " => Derived's destructor is NOT called, leading to a memory leak!" << std::endl;
return 0;
}
실행 결과
1. Base 소멸자가 virtual
일 경우
Derived
의 소멸자가 먼저 호출되어 동적 할당된 리소스(_pResource
)가 정상적으로 해제되었고, 그 후 Base
의 소멸자가 호출되었다.
2. Base 소멸자가 virtual
이 아닐 경우 (코드를 수정하여 테스트)
Base
의 소멸자만 호출되고 Derived
의 소멸자는 호출되지 않았다. 이로 인해 Derived
생성자에서 new
로 할당했던 메모리가 delete
되지 않아 메모리 누수가 발생한다.
'C++' 카테고리의 다른 글
템플릿 (2) | 2025.09.01 |
---|---|
malloc/free 와 new/delete (1) | 2025.08.26 |
Class와 구조체의 차이 (1) | 2025.08.18 |
[단계별로 IOCP 실습] 6단계 효율적인 Send 구현 (1-Send 구현하기) (3) | 2025.08.07 |
[단계별로 IOCP 실습] 5단계 효율적인 Send 구현 (1-Send 구현하기) (3) | 2025.08.06 |