Author:zfive5(zhaozidong)
Email:zfive5@yahoo.com.cn
最近同住的朋友忙着找工作,他C++的问题看了一堆,我也受其影响复习了一些C++知识, virtual析构听他说“点播率还挺高的”,所以拿来仔细研究,求个深解!
首先,定义3个类,注意了析构函数的virtual 说明
class ZF5_1
{
public:
ZF5_1()
{
}
virtual ~ZF5_1()
{
printf("Hello ZF5_1\r\n");
}
};
class ZF5_2:public ZF5_1
{
public:
ZF5_2()
{
};
virtual ~ZF5_2()
{
printf("Hello ZF5_2\r\n");
};
};
class ZF5_3:public ZF5_2
{
public:
ZF5_3()
{
};
virtual ~ZF5_3()
{
printf("Hello ZF5_3\r\n");
};
};
然后执行代码:
ZF5_1 *p5=new ZF5_3;
delete p5;
1.如果都有virtual
输出:
Hello ZF5_3
Hello ZF5_2
Hello ZF5_1
2.如果都去掉virtual
输出:
Hello ZF5_1
3.如果只去掉ZF5_2的virtual
输出:
Hello ZF5_3
Hello ZF5_2
Hello ZF5_1
第一种情况
虽然delete是基类指针,但由于有virtual析构声明,所以delete调用的派生类的析构(派生类用自己的析构函数指针重写虚函数表),然后派生类析构完成后再调用直接基类析构(C++就是这样规定的,编译器隐含这个过程,通过看汇编代码,一切机理ok),这样一级级的调用,就出现了第一种输出!
第二种情况
由于没有virtual修饰,所以delete调用是默认的ZF5_1析构,出现第二种输出就理所当然了。
第三种情况
由于ZF5_1用virtual修饰,所以以后所有它的派生类用自己的析构函数指针重写虚函数表里的对应的位置,以至出现与第一种情况相同的结果!
有一种情况没有表达,就是如果定义如下:
virtual ZF5_1
ZF5_2
ZF5_3
ZF5_4
ZF5_2 *p1=new Z5_4;
delete p1;
输出:
Hello ZF5_4
Hello ZF5_3
Hello ZF5_2
Hello ZF5_1
这种情况在VC6下,只要有基类virtual声名,以后的派生类都自动加上virtual,不知道gcc和cb是怎样处理这种情况的!有机会验证一下,对了很有可能是C++标准规定,看来还得先去翻翻C++ Primer了。
附
ZF5_3析构完成后默认调用ZF5_2析构汇编代码:(VC6环境下)
52: ~ZF5_3()
53: {
004013E0 push ebp
004013E1 mov ebp,esp
004013E3 sub esp,44h
004013E6 push ebx
004013E7 push esi
004013E8 push edi
004013E9 push ecx
004013EA lea edi,[ebp-44h]
004013ED mov ecx,11h
004013F2 mov eax,0CCCCCCCCh
004013F7 rep stos dword ptr [edi]
004013F9 pop ecx
004013FA mov dword ptr [ebp-4],ecx
004013FD mov eax,dword ptr [ebp-4]
00401400 mov dword ptr [eax],offset ZF5_3::`vftable' (00423044)
54: printf("Hello ZF5_3\r\n");
00401406 push offset string "Hello ZF5_3\r\n" (00424050)
0040140B call printf (00401e40)
00401410 add esp,4
55: };
00401413 mov ecx,dword ptr [ebp-4]
00401416 call @ILT+145(ZF5_2::~ZF5_2) (00401096)
0040141B pop edi
0040141C pop esi
0040141D pop ebx
0040141E add esp,44h
00401421 cmp ebp,esp
00401423 call __chkesp (00401e00)
00401428 mov esp,ebp
0040142A pop ebp
0040142B ret