출처 : Effective C++. 항목 7 : 다형성을 가진 기본클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.
상속을 사용한 객체를 할당하고 해제할때 기반클래스의 포인터를 사용하여 할당 및 해제를 사용하는 경우가 무척 많다. 이 경우 기반 클래스 및 자식 클래스의 소멸자가 virtual 이 붙어야 하는 경우와 붙지 말아야 하는 경우가 있다.
위와 같은 코드가 있을 경우 Base 의 포인터를 이용해 할당을 하고 Base형의 포인터를 이용해 메모리를 해제한다.
이 경우의 문제는 Base의 소멸자가 virtual 이 아니라면 소멸되는 과정에서 상속받은 클래스의 소멸자가 정상적으로 호출되지 않게 되서 정의되지 않는 동작이 일어나게 된다. 즉 기반클래스의 소멸자는 virtual로 선언 되어야 한다. 뒤집어 이야기 한다면 virtual 로 선언되지 않은 소멸자를 같는 클래스는 기반클래스(일반적인 의미의 인터페이스를 제공하는)로 사용되 않을 확률이 높다.
class Base {
// …
};class Derived : public Base {
// …
};…
Base* object = new Derived();
// object 사용
delete object;
이 경우의 문제는 Base의 소멸자가 virtual 이 아니라면 소멸되는 과정에서 상속받은 클래스의 소멸자가 정상적으로 호출되지 않게 되서 정의되지 않는 동작이 일어나게 된다. 즉 기반클래스의 소멸자는 virtual로 선언 되어야 한다. 뒤집어 이야기 한다면 virtual 로 선언되지 않은 소멸자를 같는 클래스는 기반클래스(일반적인 의미의 인터페이스를 제공하는)로 사용되 않을 확률이 높다.
예를 들어
은 딱 64비트 크기에 맞는 크기다. 이 경우 64비트 레지스터에 쏙 들어감으로써 최적화된 코드라고 볼수도 있다. 하지만 이 Point 클래스의 소멸자를 virtual 로 선언하는 순간 각 Point 클래스의 객체는 버추얼테이블 포인터를 가져야 하며 64비트 크기를 넘어서게 된다. 즉 이런 경우에는 가상소멸자는 어울리지 않는다.
class Point{
public:
Point(int x, int y);
~Point();
private:
int x, y;
};
다시 한번 강조하면 클래스가 가상함수를 하나 이상 가지고 있는 경우에만 소멸자를 virtual로 선언한다 를 기억해 둬야 한다.
그런데 많은 실수중에 하나가 C++ , STL 에서 제공하는 표준 클래스들을 상속받는 경우다.
std::string, std::vector 등의 클래스들은 virtual 소멸자를 갖지 않으므로 이들을 상속받아 추가적인 내용을 구현하는 경우 기반클래스의 포인터로 할당하고 해제할 경우 정의되지 않는 동작이 일어나므로 이런식으로 사용해서는 안된다.
ex) 부스트의 noncopyable
class GoodString: public std::string{
};
// …
GoodString *pss = new GoodString(“어쩌구저쩌구”);
std::string* ps = pss;
delete ps; // GoodString의 소멸자가 호출되지 않음. 에러발생.
사견으로 위와 같이 기반클래스의 포인터로 메모리 해제만 안하면 별 문제없는것 같다. 즉 처음부터 끝까지 상속받은 클래스의 포인터를 사용하면 된다.
물론 virtual 소멸자가 아니지만 기반클래스로 제작된 클래스도 있다.ex) 부스트의 noncopyable