C++虚析构函数的介绍与解析

来源:互联网 发布:网络上鹅是什么意思 编辑:程序博客网 时间:2024/06/05 15:46

       当使用基类指针释放派生类的对象时,需要将基类的析构函数声明为virtual,这是C++的一条规范,今天花了些时间探讨了下。

先构建两个类:father类 和son类,简单定义下:

class Father
{
public:
 Father(void);
public:
 virtual ~Father(void);
 void description();
private:
 int m_i_gen;
};

子类

class Son: public Father
{
public:
 Son(void);
public:
  ~Son(void);

 void description();

private:
 int m_i_num;

};

问题一:虚析构函数与普通虚函数有什么区别

    首先,在C++中,每个类都只有一个析构函数,比如在子类Son中,便不能再添加父类father的虚函数virtual ~Father(void)的实现版本,这与普通虚函数是不同,普通虚函数是要在派生类中,重新给出父类中虚函数的实现,并且函数名相同。

问题二:虚析构函数的实现机制

   虚析构函数,也是通过vftable来实现的,实际上当父类的析构函数声明为虚函数时,子类的析构函数也变成了虚函数(虽然两者函数名不同),即告诉编译器,我和我的派生类都需要动态(运行时)完成析构函数执行。

     下面通过代码来进行验证

Father::Father(void)
{
 m_i_gen=1;
 CString str;
 str.Format("This is Father's constructor call");
 std::cout<<str<<"\n";

}

Father::~Father(void)
{
 std::cout<<"Father destroy"<<"\n";
}
void Father::description()
{
 std::cout<<"I am a father"<<"\n";
}
子类

Son::Son(void)
{
 m_i_num=1;
 CString str;
 str.Format("This is Son's constructor call");
 std::cout<<str<<"\n";
}

Son::~Son(void)
{
 std::cout<<"Son destroy"<<"\n";
}

void Son::description()
{
 std::cout<<"I am a son"<<"\n";
}
void  Son:: test()
{
 Father fath;
 fath.description();
}

主函数

int _tmain(int argc, _TCHAR* argv[]){

  Father fath;
 Son son;
 Father *f;
 Son *s;
 f = new Son();
 delete f;
 return 0;
}

依旧是执行之后追踪变量s

-  s 0x0040ad22 {m_i_num=-1259292477 } Son *
-  Father {m_i_gen=1213847799 } Father
-  __vfptr 0xc01bd8f7 *
  [0] CXX0030: 错误: 无法计算表达式的值 
  m_i_gen 1213847799 int
  m_i_num -1259292477 int
出现了小插曲,vfptr的值竟然没发追踪到,可能是因为对象是在堆中申请的内存空间,所以进入反汇编

确定在004057C4 这条语句,调用了4198885 (十进制)的函数,即类Son的析构函数~Son();

 

对于栈上的对象son,其变量追踪为

-  son {m_i_num=1 } Son
-  Father {m_i_gen=1 } Father
-  __vfptr 0x00434738 const Son::`vftable' *
  [0] 0x004011e5 Son::`vector deleting destructor'(unsigned int) *
  m_i_gen 1 int
  m_i_num 1 int
其中将vfptr中[0]内的0x004011e5换算成十进制,刚好就4198885,也就是说虽然没有直接追踪到对象s的vfptr[0]的值,但其实际调用的是地址为4198885的函数,这也就是栈上的对象son的vfptr[0]的值,即s对象是通过虚析构函数,在若不声明虚析构函数的情况下,会调用Father类的析构函数,编程了通过vfptr(0)来调用了Son类的析构函数。

这与直接son对象和father对象直接调用析构函数是完全不一样的:

004057E7  mov         dword ptr [ebp-3Ch],0
004057EE  mov         byte ptr [ebp-4],0
004057F2  lea         ecx,[ebp-20h]
004057F5  call        Son::~Son (401217h)
004057FA  mov         dword ptr [ebp-4],0FFFFFFFFh
00405801  lea         ecx,[ebp-28h]
00405804  call        Father::~Father (4012E4h)
00405809  mov         eax,dword ptr [ebp-3Ch]

这两个析构函数的调用,在编译时,已确定传入地址。

 

总的来说,使用基类指针,在堆上释放派生类对象,就必须将基类指针声明为虚函数,后续的工作C++会帮忙完成。C++使用“按照绝对位置查表”的方式实现多态,确实保证好较好的效率,但是相较于按“函数名称查表”的实现机制,更不够能体现面向对象模型语言的特点。

 


 

原创粉丝点击