긴 말보다 몇줄의 코드가 좋을 것 같다.
간단한 인터페이스와 구현 클래스를 예로 들어보겠다.
class IAbc // 단순한 인터페이스 클래스
{
public:
virtual void Print() = 0; // 단순한 인터페이스 하나 선언
};
class CAbc : public IAbc
{
IAbc* m_pNext;
public:
CAbc() {cout << "CAbc Constructor" << endl;} // 생성자가 불리는지 확인
~CAbc() {cout << "CAbc Destructor" << endl;} // 소멸자가 불리는지 확인
void Print() {cout << "CAbc" << endl;} // IAbc의 인터페이스 구현
};
이와 같이 설계를 했다면 아래 코드와 같이 사용할 것이다.
(편의상 생성 패턴은 생략한다.)
IAbc *pAbc;
pAbc = new CAbc;
pAbc->Print();
delete pAbc;
첫줄은 변수 선언이므로 아무 변화 없다.
두번째 줄에서 CAbc의 객체를 실제 만들었으므로 CAbc의 객체가 생성되며, 따라서 CAbc의 생성자가 호출된다.
세번째 줄에서 pAbc는 IAbc의 변수이나 virtual로 선언된 멤버 함수를 호출하기 때문에, 실제 객체인 CAbc에서 구현한 Print가 호출된다.
네번째 줄이 문제이다. pAbc의 실제 객체가 CAbc란 정보가 없기 때문에 pAbc는 C++ 컴파일러 내부에서 자동으로 생성한 기본 소멸자인 ~IAbc()만 호출하고 종료한다. 만약 CAbc의 소멸자에서 메모리 해제 등의 일을 구현한 경우 이것은 무시된다. 따라서 인터페이스 클래스의 경우에도 기본 생성자, 소멸자는 반드시 정의하여야 한다.
올바른 IAbc의 코드는 아래와 같다.
class IAbc // 단순한 인터페이스 클래스
{
public:
IAbc() {};
virtual ~IAbc() {};
virtual void Print() = 0; // 단순한 인터페이스 하나 선언
};
생성자의 경우는, 무엇을 만들지를 정한 후에 객체를 생성하므로 당연히 필요한 생성자가 불리게 된다. 그러나 소멸자의 경우는 어느 포인터를 사용하여 소멸자를 호출할 지 알 수 없으므로 위와 같이 가상함수로 소멸자를 정의하여야 한다. 물론 ~IAbc의 가상함수를 정의했다고 하여, CAbc에서도 ~IAbc()를 오버라이드할 필요는 없다. CAbc에서는 ~CAbc()를 구현하면 알아서 IAbc의 소멸자가 호출되는 경우 CAbc의 소멸자가 불리게 된다.


