항목 7 : 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
시간 기록에 관련된 TimeKeeper이라는 클래스를 선언하겠습니다.
1
2
3
4
5
6
7
8
9
10
|
class TimeKeeper{
public :
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock : public TimeKeeper { ... };
class WaterClock : public TimeKeeper { ... };
class WristWatch : public TimeKeeper { ... };
|
이 클래스의 혜택을 받는 사용자들은 시간 정보에 접근하고 싶어 합니다. 시간 계산이 어떻게 되는지에 대해서는 신경 쓰고 싶지 않고요. 사정이 이렇기 때문에, 어떤 시간기록 객체에 대한 포인터를 손에 넣는 용도로 팩토리 함수(새로 파생된 파생 클래스 객체에 대한 기본 클래스 포인터를 반환하는 함수)를 만들어 두면 좋을 것 같습니다.
1
|
TimeKeeper* getTimeKeeper(); // TimeKeeper에서 파생된 클래스를 통해 동적으로 할당된 객체의 포인터를 반환합니다.
|
팩토리 함수의 기존 규약을 그대로 따라간다면 getTimeKeeper 함수에서 반환되는 객체는 힙에 있게 되므로, 결국 메모리 및 기타 자원의 누출을 막기 위해 해당 객체를 적절히 삭제해야 합니다.
1
2
3
|
TimeKeeper *ptk = getTimeKeeper(); // TimeKeeper클래스 계통으로부터 동적으로 할당된 객체를 얻습니다.
... // 이 객체를 사용합니다.
delete ptk; // 자원 누출을 막기 위해 해제(삭제)합니다.
|
이 코드에서 발생할 수 있는 문제는 getTimeKeeper 함수가 반환하는 포인터가 파생 클래스 객체에 대한 포인터라는 점과 이 포인터가 가리키는 객체가 삭제될 떄는 기본 클래스 포인터를 통해 삭제된다는 점, 그리고 결정적으로 기본 클래스에 들어있는 소멸자가 비가상 소멸자라는 점입니다.
C++의 규정에 의하면, 기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때 그 기본 클래스에 비가상 소멸자가 들어 있으면 프로그램 동작은 미정의 사항이라고 되어 있습니다.
대개 그 객체의 파생 클래스 부분이 소멸되지 않게 되지요.
이 문제를 해결하는 방법은 간단합니다.
기본 클래스에게 가상 소멸자를 주면 됩니다.
1
2
3
4
5
6
7
8
9
10
|
class TimeKeeper{
public :
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk; // 이제 제대로 동작합니다.
|
기본 클래스의 손에 가상 소멸자를 쥐어 주자는 규칙은 다형성을 가진 기본 클래스, 그러니까 기본 클래스 인터페이스를 통해 파생 클래스 타입의 조작을 허용하도록 설계된 기본 클래스에만 적용된다는 사실을 알아야 합니다.
꼭 잊지 말아야 할 것!
1. 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 한다. 즉, 어떤 클래스가 가상 함수를 하나라도 갖고 있으면, 이 클래스의 소멸자도 가상 소멸자이어야 한다.
2. 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말아야 한다.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2020.01.09 |
---|---|
[Effective C++] 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2020.01.08 |
[Effective C++] 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2020.01.06 |
[Effective C++] C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2020.01.05 |
[Effective C++] 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2020.01.04 |