Item 7: 다형성을 가진 기본클래스의 소멸자는 반드시 가상소멸자로 선언하자
1. C++ 규정에서, 기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때 그 기본 클래스에 비가상 소멸자가 있으면 어떻게 되는가?
프로그램의 동작은 미정이다. 보통의 경우 파생 클래스의 소멸자가 호출되지 않는다.
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristClock: public TimeKeeper { ... };
보통의 경우 여러 시계 종류중 하나를 만들어 주는 팩토리 함수를 사용한다.
Timekeeper* getTimeKeeper(); // 보통 new 연산자를 이용해 힙에 객체를 생성해서 준다.
TimeKeeper *ptk = getTimeKeeper(); // 팩토리 함수를 통해
// TimeKeeper 계열의 시계를 얻어온다.
... // 이를 사용한다.
delete ptk; // 사용후 삭제
마지막 delete ptk 에서 문제가 발생한다. 여기에서 기본 클래스인 TimeKeepr의 소멸자가 비가상함수이어서 파생클래스의 소멸자가 제대로 호출되지 않는다.
2. 파생 클래스의 소멸자가 제대로 호출되게 하려면 어떻게 해야 하나?
기본 클래스의 소멸자를 가상함수로 선언해 준다.
class TimeKeepr {
public:
TimeKeeper() {};
virtual ~TimeKeeper() {};
...
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk; // 이제 문제 없다.
3. 가상 멤버 함수를 하나라도 갖는 클래스는 반드시 가상 소멸자를 가져야 하는가?
대부분의 경우 맞다.
4. 가상 소멸자를 갖고 있지 않는 클래스를 보면 어떤 생각을 하면 되나?
이 클래스는 기본 클래스로 쓰일 의지가 없구나.
5. 기본 클래스로 사용될 의도가 없는 클래스가 만약에 가상 함수를 가지면 안좋은 점은?
가상 함수를 갖는 클래스는 해당 멤버변수 외 추가적인 자료구조가 들어가게 된다.
vptr[virtual table pointer]이라는 이름의 포인터이다.(32/64bit)
가상 함수를 갖는 클래스는 실행중 어떤 가상함수로 연결되어야 하는지를 나타내는 vtbl[virtual table]이 필요하다.
vptr은 이 vtbl을 가리키는 포인터이다.
6. 가상 소멸자는 언제 선언하는 것이 좋은가?
가상 멤버 함수가 하나라도 있으면 반드시 소멸자를 가상으로 선언하자.
7. STL 컨테이너 타입(vector, list, set, map)을 상속해서 사용해도 되나?
좋은 생각이 아니다.
이 클래스들은 가상 소멸자가 정의되어 있지 않다.
class SpecialString: public std::string {
...
};
SpecialString *pss = new SpecialString("Impending Doom");
std::string *ps;
...
ps = pss;
...
delete ps; // 정의되지 않은 행동 발생.
// SpecialString의 소멸자가 호출안되 자원누출.
8. 추상클래스란?
순수가상함수를 하나라도 갖는 클래스로 자신에 대한 인스턴스 생성불가.
9. 순수 가상 함수란?
function_name = 0; 으로 선언하는 함수.
하지만 정의를 가질 수 있다. 왜냐하면 해당 함수를 파생클래스에서 호출할 수도 있기 때문. 순수 가상 소멸자의 경우 정의도 필요하다.
10. 순수 가상 소멸자를 주로 사용하는 경우는?
어떤 클래스를 추상클래스로 만들고 싶은데, 딱히 순수 가상 함수로 만들 함수가 없을 때 소멸자를 순수 가상 소멸자로 만들면 편하다.
이때 순수 가상 소멸자는 정의를 갖아야 한다.
11. 왜 정의를 갖어야 하나?
소멸자가 동작하는 순서는 이렇다. 상속 계통 구조에서 가장 말단에 있는 파생 클래스의 소멸자가 먼저 호출되고, 기본 클래스쪽으로 거쳐 올라가면서 소멸자가 호출된다. 그래서 만약에 소멸자를 정의하지 않으면 링크에러가 발생한다.
12. 기본 클래스로 쓰일 수 있지만 다형성을 지원하지 않토록 설계된 클래스도 있는가?
그렇다. 이런 클래스는 기본 클래스의 인터페이스를 통해서 파생 클래스 객체의 조작이 허용되지 않는다. 이런 클래스들에서 가상 소멸자가 없는 이유는 이것 때문이다.
정리
- 다형성을 가진 기본 클래스는 반드시 가상소멸자를 선언해야 한다. 즉, 어떤 클래스가 가상함수를 하나라도 갖고 있으며, 이 클래스의 소멸자도 가상 소멸자이어야 한다.
- 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말자.