로또
7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자. 본문
다루는 내용
기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때, 기본 클래스에 비가상 소멸자가 들어있다면 프로그램의 동작은 정해져있지 않다.
소멸자를 가상 함수로 선언하지 않은 경우
#include <iostream>
using namespace std;
class TimeKeeper{
public:
TimeKeeper(){
cout << "TimeKeeper Constructor" << endl; // 1
}
~TimeKeeper(){
cout << "TimeKeeper Destructor" << endl; // 4
}
};
class AtomicClock: public TimeKeeper{
public:
AtomicClock(){
cout << "AtomicClock Constructor" << endl; // 2
}
~AtomicClock(){
cout << "AtomicClock Destructor" << endl; // 3
}
};
// 팩토리 함수
class CTimeFactory{
public:
static TimeKeeper* CreateAtomic(){
return new AtomicClock;
}
};
int main(){
TimeKeeper* ptk = CTimeFactory::CreateAtomic();
delete ptk;
}
위 코드는 기본 클래스 포인터(TimeKeeper* ) 로 파생 클래스의 객체(AtomicClock)를 가리켜 사용하고 있다.
ptk 변수를 필요에 따라 사용한 이후, 메모리 해제를 위해 delete를 호출하면 다음과 같은 결과를 얻을 수 있다.

사진과 같이, AtomicClock의 Destructor가 호출되지 않고 프로그램이 종료되었다.
C++ 규정에 의하면 비가상 소멸자를 가지는 기본 클래스 포인터를 통해 파생 클래스 객체를 삭제하는 행위는 프로그램 동작에서 미정의 사항이라고 한다. 그리고 대개 위처럼 객체의 파생 클래스 부분이 사라지지 않고 여전히 존재한다.
이러한 문제를 해결하기 위해서는 기본 클래스의 소멸자를 가상 함수로 선언하면 된다.
소멸자를 가상 함수로 선언한 경우
class TimeKeeper{
public:
TimeKeeper(){
cout << "TimeKeeper Constructor" << endl; // 1
}
virtual ~TimeKeeper(){
cout << "TimeKeeper Destructor" << endl; // 4
}
};
기본 클래스 소멸자에 virtual 키워드를 붙여 가상 함수로 바꾸어주었다. 실행 결과는 다음과 같다.

자주 쓰이는 string 클래스와 vector, list, set, unordered_map와 같은 STL 또한 가상 소멸자가 없는 클래스이므로 주의하도록 하자. (java는 final, c#에는 sealed 처럼 파생 방지 매커니즘이 존재하지만 C++에는 그렇지 않음.)
가상함수 테이블
기본 클래스로 쓰이기 위한 클래스는 대개 소멸자 외에도 가상 멤버 함수가 존재할 수 있다. 이는 파생 클래스를 구현할 때, 해당 함수를 파생 클래스의 역할에 따라 오버라이딩을 허용한다는 의미이다.
가상 함수를 C++에서 구현하려면 클래스에 별도의 자료 구조가 필요하다.
이는 프로그램 실행 중에 주어진 객체에 대해 어떤 가상 함수를 호출해야 하는지를 결정하는 데 쓰이는 정보로, 실제로는 포인터의 형태를 취하는 것이 대부분이고 대개 v-pointer(가상함수 테이블 포인터) 라고 불린다. v-pointer는 가상 함수의 주소, 즉 포인터들의 배열을 가리키고 있으며 가상 함수 테이블 포인터의 배열은 v-table(가상함수 테이블) 이라고 불린다. 가상 함수를 하나라도 갖고 있는 클래스는 반드시 그와 관련된 v-table을 갖고 있다.
어떤 객체에 대해 어떤 가상 함수가 호출되려고 하면, 호출되는 실제 함수는 그 객체의 v-pointer이 가리키는 v-table에 따라 결정된다.
순수 가상 소멸자
순수 가상함수를 가지는 클래스는 추상 클래스이고, 추상 클래스는 그 자체로 객체를 생성할 수 없다. 추상 클래스는 본래 기본 클래스로 쓰일 목적으로 만들어진 클래스이고, 기본 클래스로 쓰이려는 클래스는 가상 소멸자를 가져야하므로 추상 클래스로 만들고 싶은 클래스에 순수 가상 소멸자를 선언하자. 또한, 기본 클래스의 소멸자는 호출되어야 하므로 정의부가 존재하여야 한다. 그렇지 않을 경우 링크 에러가 발생한다.
이것만은 잊지 말자!
- 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 합니다. 즉, 어떤 클래스가 가상 함수를 하나라도 갖고 있으면, 이 클래스의 소멸자도 가상 소멸자여야 합니다.
- 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말아야 합니다.
'책 > Effective C++' 카테고리의 다른 글
| 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자. (0) | 2023.11.07 |
|---|---|
| 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자. (0) | 2023.11.06 |
| 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자. (0) | 2023.11.03 |
| 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. (1) | 2023.11.02 |
| 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자. (1) | 2023.11.01 |