부모 클래스 생성자 또는 소멸자에서 가상함수를 호출하면 부모 클래스에 있는 가상함수를 호출한다. 이유는 간단하다. 자식클래스부분을 아직 생성하지 못했거나 이미 소멸했기 때문이다. 예를 들어보자.
#include <iostream>귀찮아서 gcc 확장인 __PRETTY_FUNCTION__을 사용했다. VC 같은데선 돌아가지 않는 소스다. 아름답게 컴파일을 하고 실행하면 다음과 같은 결과를 얻을 수 있다.
using namespace std;
class CParent
{
public:
CParent()
{
cout << __PRETTY_FUNCTION__ << endl;
call_virtual_function();
}
virtual ~CParent()
{
cout << __PRETTY_FUNCTION__ << endl;
call_virtual_function();
}
void call_virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
virtual_function();
}
virtual void virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
}
};
class CChild : public CParent
{
public:
CChild()
{
cout << __PRETTY_FUNCTION__ << endl;
}
virtual ~CChild()
{
cout << __PRETTY_FUNCTION__ << endl;
}
virtual void virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
}
};
int
main(int argc,char* argv[])
{
cout << "Case1" << endl;
CParent* p = new CChild;
delete p;
cout << "Case2" << endl;
CChild* c = new CChild;
delete c;
return 0;
}
Case1호출순서는 아래와 같다.
CParent::CParent()
void CParent::call_virtual_function() const
virtual void CParent::virtual_function() const
CChild::CChild()
virtual CChild::~CChild()
virtual CParent::~CParent()
void CParent::call_virtual_function() const
virtual void CParent::virtual_function() const
Case2
CParent::CParent()
void CParent::call_virtual_function() const
virtual void CParent::virtual_function() const
CChild::CChild()
virtual CChild::~CChild()
virtual CParent::~CParent()
void CParent::call_virtual_function() const
virtual void CParent::virtual_function() const
CParent() → CParent::call_virtual_function() → CParent::virtual_function() → CChild() → 객체 생성 완료!아주 당연한 이치다. CParent가 call_virtual_function()을 통해 virtual_function()을 호출하려고 내부 virtual table을 뒤적였을 것이다. 그러나 virtual table에는 아직 CChild가 없기 때문에(CChild 생성자가 그 역을 한다) 당연히 CParent::virtual_function()을 호출했을 것이다.
~CChild() → ~CParent() → CParent::call_virtual_function() → CParent::virtual_function() → 객체 해체 완료!
반대도 마찬가지이다. CChild 소멸자가 virtual table에서 CChild를 삭제하였기 때문에 CParent::virtual_function()을 호출하는 것이다.
virtual table과 생성 관계를 증명하기 위해 한 가지 실험을 더 해보기로 하자.
#include <iostream>좀 헷갈리지? CParent 기능 몇개를 CGrandParent로 옮기고 CParent가 CGrandParent를 상속 받았을 뿐이다. (이 예제에서 굳이 CChild는 필요 없는데...) 역시나 CParent()가 call_virtual_function()을 호출하도록 했다.
using namespace std;
class CGrandParent
{
public:
CGrandParent()
{
cout << __PRETTY_FUNCTION__ << endl;
}
virtual ~CGrandParent()
{
cout << __PRETTY_FUNCTION__ << endl;
}
void call_virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
virtual_function();
}
virtual void virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
}
};
class CParent : public CGrandParent
{
public:
CParent()
{
cout << __PRETTY_FUNCTION__ << endl;
call_virtual_function();
}
virtual ~CParent()
{
cout << __PRETTY_FUNCTION__ << endl;
call_virtual_function();
}
virtual void virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
}
};
class CChild : public CParent
{
public:
CChild()
{
cout << __PRETTY_FUNCTION__ << endl;
}
virtual ~CChild()
{
cout << __PRETTY_FUNCTION__ << endl;
}
virtual void virtual_function(void) const
{
cout << __PRETTY_FUNCTION__ << endl;
}
};
int
main(int argc,char* argv[])
{
cout << "Case3" << endl;
CGrandParent* g = new CChild;
delete g;
return 0;
}
실행결과는 아래와 같다.
Case3CParent()가 CGrandParent::call_virtual_function()을 호출하기 전에 CParent를 virtual table에 등록했으며, 따라서 CGrandParent::call_virtual_function()이 virtual_function()을 찾을 때 CParent::virtual_function()을 찾는다. 따라서 CParent::virtual_function()을 호출한다. 그러나 CChild는 virtual_table에 등록하지 않았기 때문에 CChild::virtual_function()을 호출하진 않는다. 반대도 마찬가지이다. 더 쓰면 손꾸락만 아프다.
CGrandParent::CGrandParent()
CParent::CParent()
void CGrandParent::call_virtual_function() const
virtual void CParent::virtual_function() const
CChild::CChild()
virtual CChild::~CChild()
virtual CParent::~CParent()
void CGrandParent::call_virtual_function() const
virtual void CParent::virtual_function() const
virtual CGrandParent::~CGrandParent()
이 문제 핵심은 clear(), destroy() 메서드 구현이다. 역시나 예를 들어보자.
#include <iostream>컴파일해서 실행한 결과는 아래와 같다.
using namespace std;
class CParent
{
public:
CParent() : p(NULL)
{
}
virtual ~CParent()
{
destory();
}
protected:
virtual void destory(void)
{
cout << __PRETTY_FUNCTION__ << endl;
if ( p )
{
delete p;
p = NULL;
}
}
protected:
int* p;
};
class CChild : public CParent
{
public:
CChild() : CParent(), p2(NULL)
{
}
virtual ~CChild()
{
// do nothing
}
protected:
virtual void destory(void)
{
cout << __PRETTY_FUNCTION__ << endl;
if ( p2 )
{
delete p2;
p2 = NULL;
}
CParent::destory();
}
protected:
int* p2;
};
int
main(int,char**)
{
cout << "Case4" << endl;
CParent* p = new CChild;
delete p;
return 0;
}
Case4만약 CChild::p2를 할당한 객체라면 메모리가 좔좔 샐 것이다. 이걸 해결할만한 방법이... 갑자기 생각 안 나므로 나중에 포스팅 해야겠다.
virtual void CParent::destory()
덧글
해결 방법은 비가상함수를 하나 만들고 소멸자에서는 비 가상함수를 호출하도록 하고 실제로 운영하는 가상함수에서는 이 비가상함수를 호출하도록 하는 수밖에..
class CChild : public CParent
{
public:
CChild() : CParent(), p2(NULL)
{
}
virtual ~CChild()
{
this->destory_internal();
}
protected:
void destory_internal(void)
{
//do destroy
}
virtual void destory(void)
{
this->destroy_internal();
}
}