728x90
반응형

C++의 다형성을 통해 우리는 공통 기능을 가진 하나의 클래스를 기본 클래스로 만들고 적절한 용도에 따라 파생시켜 사용할 수 있으므로, 이렇게 파생시킨 객체를 쉽게 사용하기 위해 이 객체에 대한 포인터를 가져오는 용도로 팩토리 함수를 사용하면 좋을 것 같다.

그럼 팩토리 함수부터 알아보자.

 

팩토리 함수

새로 생성된 파생 클래스 객체에 대한 기본 클래스 포인터를 반환하는 함수로 파생 클래스의 객체를 new로 할당하기 때문에 메모리를 적절히 삭제해야 한다.

하지만 잘 못 사용하면 문제가 될 수 있는데, 아래의 예시를 보고 문제가 되는 부분을 찾아보자.

class TimeKeeper
{
public:
	TimeKeeper();
	~TimeKeeper();
}

class AtomicClock: public TimeKeeper {...};
class WaterClock: public TimeKeeper {...};

----
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;

여기서의 문제는 getTimeKeeper함수가 반환하는 포인터가 파생 클래스 객체에 대한 포인터라는 점과 이 포인터가 가리키는 객체가 삭제될 때는 기본 클래스 포인터를 통해 삭제된다는 점, 그리고 기본 클래스에 들어있는 소멸자가 비가상 소멸자라는 점이다.

이렇게 되면 대게 그 객체의 파생 클래스 부분은 소멸하지 않게 되는데, 기본 클래스 부분은 소멸되므로 부분 소멸 객체의 신세로 남고 만다.

 

해결 방법

기본 클래스의 소멸자를 가상 소멸자로 변경하는 것이다. 이 단순한 동작 하나로 객체는 전부 소멸한다.

class TimeKeeper
{
public:
	TimeKeeper();
	virtual ~TimeKeeper();
}

하지만 모든 클래스에 가상 소멸자를 붙이는 건 옳지 않다.

왜냐하면 virtual을 사용하게 되면, 클래스에서는 가상 함수 테이블(vtbl)이 포함되고 객체는 가상 함수 포인터(vptr)를 갖게 되어 객체의 크기가 커지게 되기 때문에 가상 소멸자는 기본 클래스에 가상 함수가 하나라도 있을 경우에 한하여 정의하고, 다형성의 의도가 없는 클래스와 다형성을 갖도록 설계되지 않은 클래스(string, STL컨테이너 타입, Uncopyable, input_iterator_tag....)는 기본 소멸자를 사용하도록 하자.

 

순수 가상 소멸자의 용도

추상 클래스 만드는데 마땅한 순수 가상 함수가 없다면 어차피 추상 클래스는 기본 클래스로 사용하기 위한 목적이라 가상 소멸자가 필요하므로, 가상 소멸자를 순수 가상 함수로 만들면 된다.

class AWOV // 추상 클래스
{
public:
	virtual ~AWOV() = 0; // 순수 가상 소멸자
}

----
AWOV::~AWOV() {} // 순수 가상 소멸자의 정의

단지, 소멸자가 동작하는 순서는 파생 클래스의 소멸자부터 호출되기 시작해서 기본 클래스 쪽으로 올라가는 형식을 띄는데 각 기본 클래스의 소멸자가 하나씩 호출될 것이다. 즉, 순수 가상 소멸자를 사용했다면 정의를 꼭 두어야 하며 정의가 없다면 링커 에러가 발생한다는 점만 알고 있으면 된다.

 

요약

  • 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 한다. 즉, 어떤 클래스가 가상 함수를 하나라도 갖고 있으면, 이 클래스의 소멸자도 가상 소멸자여야 한다.
  • 기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않은 클래스에는 가상 소멸자를 선언하지 말아야 한다.
728x90
반응형