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

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

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

 

[cpp]  view plain copy
  1. class student  
  2.  
  3. public 
  4.     int *m_pInt;  
  5.     student()  
  6.      
  7.         m_pInt new int[10];   //1  
  8.         memset(m_pInt, 0, 10*4);  
  9.      
  10.   
  11.     ~student()  
  12.                   //3  
  13.         delete []m_pInt;  
  14.      
  15.   
  16. };  
  17.   
  18. class GradeOneStue:public student  
  19.  
  20. public 
  21.     int m_iNum;  
  22.     GradeOneStue()            
  23.                   //2  
  24.         m_iNum 1;  
  25.      
  26.   
  27.     ~GradeOneStue()  
  28.                   //4  
  29.         m_iNum 0;  
  30.      
  31. };  
  32.   
  33. int _tmain(int argc, _TCHAR* argv[])  
  34.  
  35.     GradeOneStue gd;  
  36.     return 0;  
  37.  
这时构造顺序是先1后2,下面是反汇编代码

 

 

[plain]  view plain copy
  1. GradeOneStue()            
  2. 00411470  push        ebp    
  3. 00411471  mov         ebp,esp   
  4. 00411473  sub         esp,0CCh   
  5. 00411479  push        ebx    
  6. 0041147A  push        esi    
  7. 0041147B  push        edi    
  8. 0041147C  push        ecx    
  9. 0041147D  lea         edi,[ebp-0CCh]   
  10. 00411483  mov         ecx,33h   
  11. 00411488  mov         eax,0CCCCCCCCh   
  12. 0041148D  rep stos    dword ptr es:[edi]   
  13. 0041148F  pop         ecx    
  14. 00411490  mov         dword ptr [ebp-8],ecx   
  15. 00411493  mov         ecx,dword ptr [this]   
  16. 00411496  call        student::student (411109h)   
  17.                       //2  
  18.         m_iNum 1;  
  19. 0041149B  mov         eax,dword ptr [this]   
  20. 0041149E  mov         dword ptr [eax+4],1   
  21.      
可以看到在执行m_iNum = 1前先调用了父类的构造函数。

 

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

 

[plain]  view plain copy
  1. ~GradeOneStue()  
  2.                       //4  
  3. 00411550  push        ebp    
  4. 00411551  mov         ebp,esp   
  5. 00411553  sub         esp,0CCh   
  6. 00411559  push        ebx    
  7. 0041155A  push        esi    
  8. 0041155B  push        edi    
  9. 0041155C  push        ecx    
  10. 0041155D  lea         edi,[ebp-0CCh]   
  11. 00411563  mov         ecx,33h   
  12. 00411568  mov         eax,0CCCCCCCCh   
  13. 0041156D  rep stos    dword ptr es:[edi]   
  14. 0041156F  pop         ecx    
  15. 00411570  mov         dword ptr [ebp-8],ecx   
  16.         m_iNum 0;  
  17. 00411573  mov         eax,dword ptr [this]   
  18. 00411576  mov         dword ptr [eax+4],0   
  19.      
  20. 0041157D  mov         ecx,dword ptr [this]   
  21. 00411580  call        student::~student (41102Dh)               
可以看到顺序和教科书上一样。

 


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

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

 

[plain]  view plain copy
  1. virtual ~GradeOneStue()  
  2.                       //4  
  3. 004116D0  push        ebp    
  4. 004116D1  mov         ebp,esp   
  5. ...  
  6. 004116F3  mov         eax,dword ptr [this]   
  7. 004116F6  mov         dword ptr [eax],offset GradeOneStue::`vftable' (415640h)   
  8.         m_iNum 0;  
  9. 004116FC  mov         eax,dword ptr [this]   
  10. 004116FF  mov         dword ptr [eax+8],0   
  11.      
  12. 00411706  mov         ecx,dword ptr [this]   
  13. 00411709  call        student::~student (411091h) ...  
可以看到,析构函数最后还是调用了父类的析构(即使是虚函数)。

 


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

 

[cpp]  view plain copy
  1. class student  
  2.  
  3. public 
  4.     int *m_pInt;  
  5.     student()  
  6.      
  7.         m_pInt new int[10];   //1  
  8.         memset(m_pInt, 0, 10*4);  
  9.      
  10.   
  11.     ~student()  
  12.                               //3  
  13.         delete []m_pInt;  
  14.      
  15.   
  16. };  
  17.   
  18. class GradeOneStue:public student  
  19.  
  20. public 
  21.     int m_iNum;  
  22.     GradeOneStue()            
  23.                       //2  
  24.         m_iNum 1;  
  25.      
  26.   
  27.     ~GradeOneStue()  
  28.                       //4  
  29.         m_iNum 0;  
  30.      
  31. };  
  32.   
  33. int _tmain(int argc, _TCHAR* argv[])  
  34.  
  35.     student *pStu new GradeOneStue();  
  36.     delete pStu;  
  37.     return 0;  
  38.  
看delete pStu处的汇编代码

 

 

[plain]  view plain copy
  1. delete pStu;  
  2. 00413726  mov         eax,dword ptr [ebp-14h]   
  3. 00413729  mov         dword ptr [ebp-0E0h],eax   
  4. 0041372F  mov         ecx,dword ptr [ebp-0E0h]   
  5. 00413735  mov         dword ptr [ebp-0ECh],ecx   
  6. 0041373B  cmp         dword ptr [ebp-0ECh],0   
  7. 00413742  je          wmain+0C9h (413759h)   
  8. 00413744  push             
  9. 00413746  mov         ecx,dword ptr [ebp-0ECh]   
  10. 0041374C  call        student::`scalar deleting destructor' (4111E5h)   
  11. 00413751  mov         dword ptr [ebp-10Ch],eax   
  12. 00413757  jmp         wmain+0D3h (413763h)   
  13. 00413759  mov         dword ptr [ebp-10Ch],0   
  14.     return 0;  
看到只调用了父类的析构函数,此时子类的析构函数没有被调用,此时子类的析构函数中虽然有调用父类析构函数的代码,但是这里直接调用的是父类的析构函数,所以这是如果子类中析构函数有释放资源的代码,这里会造成这部分资源不被释放,有可能造成内存泄露

 


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

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

 

[plain]  view plain copy
  1. delete pStu;  
  2. 004114E6  mov         eax,dword ptr [ebp-14h]   
  3. 004114E9  mov         dword ptr [ebp-0E0h],eax   
  4. 004114EF  mov         ecx,dword ptr [ebp-0E0h]   
  5. 004114F5  mov         dword ptr [ebp-0ECh],ecx   
  6. 004114FB  cmp         dword ptr [ebp-0ECh],0   
  7. 00411502  je          wmain+0D9h (411529h)   
  8. 00411504  mov         esi,esp   
  9. 00411506  push             
  10. 00411508  mov         edx,dword ptr [ebp-0ECh]    //edx等于pStu,指向new出来的子类对象  
  11. 0041150E  mov         eax,dword ptr [edx]         //将edx指向的dword传给eax,eax现在是保存的虚函数表指向的地址  
  12. 00411510  mov         ecx,dword ptr [ebp-0ECh]   
  13. 00411516  mov         edx,dword ptr [eax]         //将虚函数表指向的第一个函数的地址传给edx,也就是唯一的一个虚析构函数的地址  
  14. 00411518  call        edx                         //调用析构函数,这个函数是子类的,这个地址构造时写入虚函数表  
由于子类的虚构函数最后会调用父类的析构函数(不管是否为virtual,子类析构函数最后都会调用父类析构函数),所以最终父类的析构函数会得到执行。(这时调用的析构函数相当于pStu->vpTable->析构函数(),这个析构函数是构造时写入的,由于构造时使用子类类型去new,此时虚函数表中析构函数的地址是子类的析构函数地址)

 


最后的结论:

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

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

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


写的不对的地方,欢迎拍砖吐舌头

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值