c++ 析构函数为虚函数的问题

来源:互联网 发布:java父类指向子类 编辑:程序博客网 时间:2024/06/01 08:25

昨天去XX公司面试,面试官问了一个关于C++类析构函数为虚函数时,如果是父类的指针用子类来new,如果发生析构时,析构函数是virtual与不是virtual有什么区别。当时答的不好,回来总结了一下,在机器上实现了一遍,终于搞明白了。记录下来,以后遇到这种情况自己一定不要犯错了

一、先看第一种最简单的情况吧,教科书上教的,析构函数不是virtual,正常定义一个子类对象

 

class student  {  public:      int *m_pInt;      student()      {          m_pInt = new int[10];   //1          memset(m_pInt, 0, 10*4);      }        ~student()      {               //3          delete []m_pInt;      }    };    class GradeOneStue:public student  {  public:      int m_iNum;      GradeOneStue()                {               //2          m_iNum = 1;      }        ~GradeOneStue()      {               //4          m_iNum = 0;      }  };    int _tmain(int argc, _TCHAR* argv[])  {      GradeOneStue gd;      return 0;  }  

这时构造顺序是先1后2,下面是反汇编代码

GradeOneStue()            00411470  push        ebp    00411471  mov         ebp,esp   00411473  sub         esp,0CCh   00411479  push        ebx    0041147A  push        esi    0041147B  push        edi    0041147C  push        ecx    0041147D  lea         edi,[ebp-0CCh]   00411483  mov         ecx,33h   00411488  mov         eax,0CCCCCCCCh   0041148D  rep stos    dword ptr es:[edi]   0041148F  pop         ecx    00411490  mov         dword ptr [ebp-8],ecx   00411493  mov         ecx,dword ptr [this]   00411496  call        student::student (411109h)       {                   //2          m_iNum = 1;  0041149B  mov         eax,dword ptr [this]   0041149E  mov         dword ptr [eax+4],1       }  

可以看到在执行m_iNum = 1前先调用了父类的构造函数。

再来看看析构时的顺序(教科书上写的是先调用子类的析构函数,在调用父类的,与构造过程相反)

 

~GradeOneStue()      {                   //4  00411550  push        ebp    00411551  mov         ebp,esp   00411553  sub         esp,0CCh   00411559  push        ebx    0041155A  push        esi    0041155B  push        edi    0041155C  push        ecx    0041155D  lea         edi,[ebp-0CCh]   00411563  mov         ecx,33h   00411568  mov         eax,0CCCCCCCCh   0041156D  rep stos    dword ptr es:[edi]   0041156F  pop         ecx    00411570  mov         dword ptr [ebp-8],ecx           m_iNum = 0;  00411573  mov         eax,dword ptr [this]   00411576  mov         dword ptr [eax+4],0       }  0041157D  mov         ecx,dword ptr [this]   00411580  call        student::~student (41102Dh)  

可以看到顺序和教科书上一样。

 二、析构函数是virtual,正常定义一个子类对象

构造函数顺序就略过了,看析构汇编代码

virtual ~GradeOneStue()      {                   //4  004116D0  push        ebp    004116D1  mov         ebp,esp   ...  004116F3  mov         eax,dword ptr [this]   004116F6  mov         dword ptr [eax],offset GradeOneStue::`vftable' (415640h)           m_iNum = 0;  004116FC  mov         eax,dword ptr [this]   004116FF  mov         dword ptr [eax+8],0       }  00411706  mov         ecx,dword ptr [this]   00411709  call        student::~student (411091h) ...  

可以看到,析构函数最后还是调用了父类的析构(即使是虚函数)。

 三、用一个父类指针去new一个子类对象,析构函数不是virtual,构造过程略过

class student  {  public:      int *m_pInt;      student()      {          m_pInt = new int[10];   //1          memset(m_pInt, 0, 10*4);      }        ~student()      {                           //3          delete []m_pInt;      }    };    class GradeOneStue:public student  {  public:      int m_iNum;      GradeOneStue()                {                   //2          m_iNum = 1;      }        ~GradeOneStue()      {                   //4          m_iNum = 0;      }  };    int _tmain(int argc, _TCHAR* argv[])  {      student *pStu = new GradeOneStue();      delete pStu;      return 0;  }  


 

看delete pStu处的汇编代码 
delete pStu;  00413726  mov         eax,dword ptr [ebp-14h]   00413729  mov         dword ptr [ebp-0E0h],eax   0041372F  mov         ecx,dword ptr [ebp-0E0h]   00413735  mov         dword ptr [ebp-0ECh],ecx   0041373B  cmp         dword ptr [ebp-0ECh],0   00413742  je          wmain+0C9h (413759h)   00413744  push        1      00413746  mov         ecx,dword ptr [ebp-0ECh]   0041374C  call        student::`scalar deleting destructor' (4111E5h)   00413751  mov         dword ptr [ebp-10Ch],eax   00413757  jmp         wmain+0D3h (413763h)   00413759  mov         dword ptr [ebp-10Ch],0       return 0;  

看到只调用了父类的析构函数,此时子类的析构函数没有被调用,此时子类的析构函数中虽然有调用父类析构函数的代码,但是这里直接调用的是父类的析构函数,所以这是如果子类中析构函数有释放资源的代码,这里会造成这部分资源不被释放,有可能造成内存泄露

 

四、用一个父类指针去new一个子类对象,析构函数是virtual,构造过程略过

这里直接看delete pStu的汇编代码

delete pStu;  004114E6  mov         eax,dword ptr [ebp-14h]   004114E9  mov         dword ptr [ebp-0E0h],eax   004114EF  mov         ecx,dword ptr [ebp-0E0h]   004114F5  mov         dword ptr [ebp-0ECh],ecx   004114FB  cmp         dword ptr [ebp-0ECh],0   00411502  je          wmain+0D9h (411529h)   00411504  mov         esi,esp   00411506  push        1      00411508  mov         edx,dword ptr [ebp-0ECh]    //edx等于pStu,指向new出来的子类对象  0041150E  mov         eax,dword ptr [edx]         //将edx指向的dword传给eax,eax现在是保存的虚函数表指向的地址  00411510  mov         ecx,dword ptr [ebp-0ECh]   00411516  mov         edx,dword ptr [eax]         //将虚函数表指向的第一个函数的地址传给edx,也就是唯一的一个虚析构函数的地址  00411518  call        edx                         //调用析构函数,这个函数是子类的,这个地址构造时写入虚函数表  

 

由于子类的虚构函数最后会调用父类的析构函数(不管是否为virtual,子类析构函数最后都会调用父类析构函数),所以最终父类的析构函数会得到执行。(这时调用的析构函数相当于pStu->vpTable->析构函数(),这个析构函数是构造时写入的,由于构造时使用子类类型去new,此时虚函数表中析构函数的地址是子类的析构函数地址)

 


最后的结论:

1、无论父类与子类的析构函数是否是virtual,子类的析构函数都会调用父类的析构函数

2、如果父类与子类的析构函数不为virtual,用一个父类指针指向一个用子类类型new的对象,delete时,直接调用父类的析构函数,这是在编译时刻就决定的。如果子类析构函数中有释放资源的代码,这是会发生资源泄漏。

3、如果父类与子类的析构函数是virtual,用一个父类指针指向一个用子类类型new的对象,delete时,这时由于是通过虚函数表调用析构函数,而虚函数表中的地址是构造时写入的,是子类的析构函数的地址,由于结论第一条,所以子类与父类的析构函数都会得到调用,不会发生资源泄漏。


 

转自:http://blog.sina.com.cn/s/blog_94021710010152cp.html

0 0
原创粉丝点击