多态
编译环境:VS2013
一、对象类型
在引入多态之前,我们先来看一下对象类型
二、多态性的概念
多态一词最初来源于希腊语,意思是具有多种形式或形态的情形,在C++中是指同样的消息被不同类型的对象接收时导致不同的行为,这里讲的消息就是指对象的成员函数的调用,而不同的行为是指不同的实现。也就是调用了不同的函数。
概念的给出总是那么的抽象,我们来通过一个具体的例子来看看什么是多态:
#include<iostream>
using namespace std;
int Add(int left, int right)
{
return left + right;
}
char Add(char left, char right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add('1', '2');
Add(1.2, 2.3);
system("pause");
return 0;
}
在此函数中,同样的消息加法运算,由于运行时传入的参数类型不同,就会调用不同类型的函数,也就是会有不同的行为响应。
多态性从系统实现的角度来讲可以划分为两类:静态多态(也叫编译时多态性)和动态多态(又称运行时多态性),以前学过的函数重载和运算符的重载属于静态多态性,在程序编译时就能决定调用的是哪一个函数,静态多态是通过函数的重载来实现的(运算符重载实际上也属于函数的重载)。动态多态性是程序运行过程中才动态地确定操作所针对的对象,运行时多态性是通过虚函数来实现的。下面我们就分别看一下静态多态和动态多态。
三、静态多态
静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推
断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
四、动态多态
动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方
法。
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
下面我们通过一个例子来实现动态多态:
#include<iostream>
#include<windows.h>
using namespace std;
class CWashRoom //基类
{
public:
void GoToManWashRoom() //去男厕
{
cout << "Man--->Please Left" << endl;
}
void GoToWomanWashRoom() //去女厕
{
cout << "Woman--->Please Right" << endl;
}
};
class CPerson //基类
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom) = 0; //纯虚函数
};
class CMan :public CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom)
{
_washRoom.GoToManWashRoom();
}
};
class CWoman :public CPerson //CPerson的派生类
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom) //对CPerson中的虚函数进行重写
{
_washRoom.GoToWomanWashRoom();
}
};
void FunTest()
{
CWashRoom washRoom;
//随机生成一个数,若此数的二进制最后一位是一,则生成一个男人对象,否则生成女人对象
for (int iIdx = 1; iIdx <= 10; ++iIdx)
{
CPerson* pPerson;
int iPerson = rand() % iIdx;
if (iPerson & 0x01)
{
pPerson = new CMan;
}
else
{
pPerson = new CWoman;
}
pPerson->GoToWashRoom(washRoom);
delete pPerson;
pPerson = NULL;
Sleep(1000);
}
}
此函数就是只有在运行时编译器才能知道生成的对象类型,为动态型多态。
动态绑定的条件:
下面程序能通过编译吗?会打印什么?
#include<iostream>
using namespace std;
class CBase
{
public:
virtual void FunTest1(int _iTest) { cout << "CBase::FunTest1()" << endl; }
void FunTest2(int _iTest) { cout << "CBase::FunTest2()" << endl; }
virtual void FunTest3(int _iTest1){ cout << "CBase::FunTest3()" << endl; }
virtual void FunTest4(int _iTest) { cout << "CBase::FunTest4()" << endl; }
};
class CDerived :public CBase
{
public:
virtual void FunTest1(int _iTest){ cout << "CDerived::FunTest1()" << endl; }
virtual void FunTest2(int _iTest){ cout << "CDerived::FunTest2()" << endl; }
void FunTest3(int _iTest1) { cout << "CDerived::FunTest3()" << endl; }
virtual void FunTest4(int _iTest1, int _iTest2)
{
cout << "CDerived::FunTest4()" << endl;
}
};
int main()
{
CBase* pBase = new CDerived;
pBase->FunTest1(0);
pBase->FunTest2(0);
pBase->FunTest3(0);
pBase->FunTest4(0);
pBase->FunTest4(0, 0);
system("pause");
return 0;
}
很显然,此程序是通不过编译的,但是,如果我们把main函数中最后一个函数调用屏蔽掉就可以通过编译了,运行后结果如下图所示:
下面我们来分析一下:
在第打印的第一行中打印了派生类的函数体内容,因为FunTest1在基类中是虚函数,在派生类中也对其进行了重写。
在打印的第二行中打印了基类的函数体内容,因为FunTest2没有在基类中被写为虚函数。
在第三行中打印了派生类的函数体内容,说明了在基类中为虚函数,在派生类中重写基类的虚函数时可以不用写virtual关键字。
第四行打印了基类的函数体内容,因为派生类中没有对基类的虚函数进行重写。
最后说说出错的那行代码,因为创建的是基类类型的对象,所以根本就不能访问派生类自己的成员函数。
最后,我们来总结一下动态绑定条件:
1、派生类必须重写基类的虚函数。
2、通过基类指针或引用调用基类的虚函数(该虚函数派生类必须要重写)
在上面的实例中,我们多次提到了重写这个概念,那重写与我们以前接触到的重载、重定义有什么区别呢?我们来通过下面这张图简单看一下:
五、纯虚函数
许多情况下,在基类中不能对虚函数给出有意义的实现,就把他说明为纯虚函数,她的实现留给该基类的派生类去做。纯虚函数在声明虚函数时被“初始化”为0的函数。在成员函数的形参后面写上=0,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
纯虚函数没有函数体,后面的“=0”并不表示函数返回值为0,他只是形式上的作用,目的是告诉编译系统“这是纯虚函数”。
纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在基类中没有保留函数的名字,则无法实现多态性。
class Person
{
virtual void Display () = 0; // 纯虚函数
protected :
string _name ; // 姓名
};
class Student : public Person
{};
总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
协变:函数返回值不同,基类返回基类对象的引用,派生类返回派生类对象的引用。
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容
易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会
出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
8、虚表是所有类对象实例共用的
六、虚表剖析
在上面我们多次提到虚表,对于有虚函数的类,编译器都会维护一张虚表,那么到底虚表在内存中是以什么样的方式存在呢?下面我们就来对不同情况下的虚表进行详细剖析。
在分类剖析之前,我们先来看看虚表对对象在内存中的影响,我们先来看看下面这段代码:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data = 10;
cout << "this=" << this << endl;
}
virtual ~Base()
{}
private:
int _data;
};
int main()
{
Base b;
cout << sizeof(b) << endl;
system("pause");
return 0;
}
我们先来看看这个Base类的大小是多少?
我们可以看到,sizeof(b)的值是8,比我们以前见过的类多出了四个字节,这到底是怎么回事呢?为了弄清楚这个问题,我们在上面也打印出了这个类在内存中的地址,我们来看看内存中那四个字节到底是什么。
在图中我们可以观察到我们给成员变量赋的值被放在了给类分配内存的下四个字节,而在前四个字节放了一个地址,我们根据这个地址在内存2追踪到他里面存放的内容,我们发现里面存放的又是一个地址,
并且这个虚表最后以0结尾,至于这个地址又是什么呢?我们在这里不多做解释,后面我们会详细介绍,至少我们现在可以得出的是有虚函数的类大小多了四个字节,这四个字节里面存了一个地址,指向了一张表,我们把这张表就叫做虚表。
在认识了虚表之后,下面我们就对不同类型下的虚表进行详细介绍:
①没有覆盖(单继承)
在上面一个例子中我们已经知道,在类类内存的前四个字节存了一个地址,指向一张表,这张表中也是一些地址,现在我们就去验证一下这些地址到底指向了哪里?现在我们就用一段代码来实现以上功能。
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data = 10;
}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
private:
int _data;
};
class Derived : public Base
{
public:
virtual void FunTest4()
{
cout << "Derived::FunTest4()" << endl;
}
virtual void FunTest5()
{
cout << "Derived::FunTest5()" << endl;
}
virtual void FunTest6()
{
cout << "Derived::FunTest6()" << endl;
}
};
typedef void(*VFP)();
void Printvfp()
{
Base b;
VFP* vfp = (VFP*)(*(int*)&b); //取虚表内的指针赋给一个函数指针变量
while (*vfp)
{
(*vfp)();
cout << (int*)vfp << endl; //打印地址
++vfp; //函数指针向后偏移
}
cout << endl;
Derived d;
VFP* vfp2 = (VFP*)(*(int*)&d);
while (*vfp2)
{
(*vfp2)();
cout << (int*)vfp << endl;
++vfp2;
}
}
int main()
{
Printvfp();
system("pause");
return 0;
}
首先我对部分程序的功能实现做简单的说明,其他的代码都不叫好理解,最不容易懂得地方估计就是我注释的那几行代码了,我们一起来看看到底表示什么意思。
在这段代码中首先我们创建了一个Base类的对象b,上面我们已经说了,他的前四个字节是一个虚表指针,下面才是自己的成员变量,我们先给出它的模型图(图一),而虚表指针指向了一个以00000000结尾的表的首地址,前面都是一些指针(图二)
我们可以看到,我在函数的外部定义了一个函数指针,而创建完类的对象后的一句比较难理解,我们来分步解析:
我们在此程序中打印了虚表指针所指向的地址的内容及地址,我们来看看运行结果吧。
以红色的线为分割线,上面的是基类的虚表指针所指向的内容,下面的是派生类中的虚表所指向的内容。因为派生类中的函数对基类中的函数没有重写,因此,这种情况被称为没有覆盖。那么有覆盖又是哪种情况呢?我们一起来看看。
②有覆盖(单继承)
同样,我们先把有覆盖的这种情况代码话,然后再通过监视窗口和内存来分析结果
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data = 10;
}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
private:
int _data;
};
class Derived : public Base
{
public:
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest5()
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest6()
{
cout << "Derived::FunTest4()" << endl;
}
};
typedef void(*VFP)();
void Printvfp()
{
Base b;
VFP* vfp = (VFP*)(*(int*)&b); //取虚表内的指针赋给一个函数指针变量
while (*vfp)
{
(*vfp)();
cout << (int*)vfp << endl; //打印地址
++vfp; //函数指针向后偏移
}
cout << endl;
Derived d;
VFP* vfp2 = (VFP*)(*(int*)&d);
while (*vfp2)
{
(*vfp2)();
cout << (int*)vfp << endl;
++vfp2;
}
}
int main()
{
Printvfp();
system("pause");
return 0;
}
这段代码和上一个例子变化不大,不同之处就是在派生类Derived中对基类的FunTest1和FunTest2进行了重写。
下面我们来分析一下结果
基类中的虚表:
在调试过程中,我通过监视窗口得到基类对象b的地址,然后输入内存查看窗口找到指向虚表的地址,然后根据虚表地址在内存2窗口中得到虚表内容,最后结合运行结果看,虚表中的地址是与基类中的虚函数的地址一一对应的。所以我们现在就可以得出虚表中存放的其实就是虚函数的地址。既然已经弄明白了虚表的指向内容,那么我们就继续用同样的方法看看派生类中的虚表内容和基类有何不同呢?
查看步骤和看基类虚表的一样,我们就不多说了,主要看看不同的地方,我们发现在运行结果中被派生类重写了的FunTest1和FunTest2调用了派生类的,而没重写的按原样写下来然后再把派生类自己的虚函数添加在后面。
下面我们就来总结一下派生类虚表的生成方式:
⑴先拷贝基类的虚函数表
⑵如果派生类重写了基类的某个虚函数,就覆盖同位置的基类虚函数
⑶跟上自己新定义的虚函数
在这里还要提醒的一点就是:通过基类的引用或指针调用虚函数时,是要调用基类还是派生类的虚函数,要根据运行时引用或指针实际引用(指向)的类型确定,调用非虚函数则无论指向何种类型,都调用的是基类的函数。
③有覆盖(多继承)
首先,像上面一样,我们先把这种情况用代码实现出来:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data1 = 1;
}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
private:
int _data1;
};
class Base1
{
public:
Base1()
{
_data2 = 2;
}
virtual void FunTest4()
{
cout << "Base1::FunTest4()" << endl;
}
virtual void FunTest5()
{
cout << "Base1::FunTest5()" << endl;
}
virtual void FunTest6()
{
cout << "Base1::FunTest6()" << endl;
}
private:
int _data2;
};
class Derived : public Base,public Base1
{
public:
Derived()
{
_data3 = 3;
}
virtual void FunTest7()
{
cout << "Derived::FunTest7()" << endl;
}
int _data3;
};
typedef void(*VFP)();
void Printvfp()
{
Derived d;
Base& b = d;
VFP* vfp = (VFP*)(*(int*)&b);
cout << "Base vir Tab" << endl;
while (*vfp)
{
(*vfp)();
++vfp;
}
cout << endl;
Base1& b1 = d;
VFP* vfp2 = (VFP*)(*(int*)&b1);
cout << "Base1 vir Tab" << endl;
while (*vfp2)
{
(*vfp2)();
++vfp2;
}
cout << endl;
VFP* vfp3 = (VFP*)(*(int*)&d);
cout << "Derived vir Tab" << endl;
while (*vfp3)
{
(*vfp3)();
++vfp3;
}
}
int main()
{
Printvfp();
system("pause");
return 0;
}
我们先来通过调试窗口和内存窗口看一下类在多继承无覆盖情况下虚表在内存中的存储和派生类Derived的模型图:
正如图中所示,图三为Derived在内存中存储的模型,红色为Base基类部分,绿色为Base1基类部分,最后的蓝色部分为Derived的数据成员存储。在多继承无覆盖的情况下,我们发现第一个基类的虚表中多了一个地址(图四中用绿线画出来部分),而这个地址到底是谁的地址呢?我们来看看运行结果
从结果中我们可以清晰的看出多出来的地址是派生类自己的虚函数的地址。
看到这里我们会发现一个小小的问题,在监视窗口中也可以看这些虚表中函数的地址,为什么还要到内存中去看呢?其实看过上面这个例子我们就会发现,多继承中派生类的虚函数地址在监视窗口是看不见的,所以说监视窗口有时候提供给我们的信息是不全面的。所以建议大家还是尽量在内存窗口查看,避免被监视窗口的假象欺骗
④有覆盖(多继承)
看完多继承的无覆盖情况,我们来继续分析多继承有覆盖的情况,源码如下:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data1 = 1;
}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
private:
int _data1;
};
class Base1
{
public:
Base1()
{
_data2 = 2;
}
virtual void FunTest4()
{
cout << "Base1::FunTest4()" << endl;
}
virtual void FunTest5()
{
cout << "Base1::FunTest5()" << endl;
}
virtual void FunTest6()
{
cout << "Base1::FunTest6()" << endl;
}
private:
int _data2;
};
class Derived : public Base, public Base1
{
public:
Derived()
{
_data3 = 3;
}
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest4()
{
cout << "Derived::FunTest4()" << endl;
}
virtual void FunTest7()
{
cout << "Derived::FunTest7()" << endl;
}
int _data3;
};
typedef void(*VFP)();
void Printvfp()
{
Derived d;
Base& b = d;
VFP* vfp = (VFP*)(*(int*)&b);
cout << "Base vir Tab" << endl;
while (*vfp)
{
(*vfp)();
++vfp;
}
cout << endl;
Base1& b1 = d;
VFP* vfp2 = (VFP*)(*(int*)&b1);
cout << "Base1 vir Tab" << endl;
while (*vfp2)
{
(*vfp2)();
++vfp2;
}
cout << endl;
VFP* vfp3 = (VFP*)(*(int*)&d);
cout << "Derived vir Tab" << endl;
while (*vfp3)
{
(*vfp3)();
++vfp3;
}
}
int main()
{
Printvfp();
system("pause");
return 0;
}
这个程序与上一个程序的区别就是在派生类中对基类的部分函数进行了重写。我们来看看虚表有何变化?
大体看去和多继承无覆盖的查不了多少,区别就在于运行结果中用蓝线画出来的部分,由此可以看出派生类的虚表和上面单继承有覆盖中的一样,都是先把基类中的虚表先复制一份,然后将重写的函数把原来的覆盖掉,再将派生类自己的虚函数加在第一个基类的虚表的后面。
⑤菱形虚拟继承(无覆盖)
源码:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
_data1 = 1;
}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
private:
int _data1;
};
class C1 :virtual public Base
{
public:
C1()
{
_data2 = 2;
}
virtual void FunTest2()
{
cout << "C1::FunTest2()" << endl;
}
private:
int _data2;
};
class C2 : virtual public Base
{
public:
C2()
{
_data3 = 3;
}
virtual void FunTest3()
{
cout << "C2::FunTest3()" << endl;
}
int _data3;
};
class Derived :public C1, public C2
{
public:
Derived()
{
_data4 = 4;
}
virtual void FunTest4()
{
cout << "Derived::FunTest4()" << endl;
}
int _data4;
};
typedef void(*VFP)();
void Printvfp()
{
Derived d;
cout << sizeof(d) << endl;
C1& c1 = d;
VFP* vfp = (VFP*)(*(int*)&c1);
cout << "Base vir Tab" << endl;
while (*vfp)
{
(*vfp)();
++vfp;
}
cout << endl;
C2& c2 = d;
VFP* vfp2 = (VFP*)(*(int*)&c2);
cout << "Base1 vir Tab" << endl;
while (*vfp2)
{
(*vfp2)();
++vfp2;
}
cout << endl;
VFP* vfp3 = (VFP*)(*(int*)&d);
cout << "Derived vir Tab" << endl;
while (*vfp3)
{
(*vfp3)();
++vfp3;
}
Base& b = d;
VFP* vfp4 = (VFP*)(*(int*)&b);
cout << "Base1 vir Tab" << endl;
while (*vfp4)
{
(*vfp4)();
++vfp4;
}
}
int main()
{
Printvfp();
system("pause");
return 0;
}
在这个程序中我先计算了派生类Derived的大小sizeof(d)=36。因为是虚拟继承,所以在有虚拟表地址的情况下多了两个偏移量地址,在内存窗口中我只分析了C1的情况,C2的与之相似。同时画出了派生类的内存分配模型图如下图:
这个例子主要就是想说明虚拟继承比其他的继承在内存中多了偏移量表。
⑥虚继承
最后我们来讨论一组特殊的继承,虚继承。
源码实现:
class Base
{
public:
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
public:
int _data1;
};
class Derived :virtual public Base
{
public:
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
int _data2;
};
typedef void(*VFP)();
void Printvfp()
{
Derived d;
d._data1 = 1;
d._data2 = 2;
Base& b1 = d;
VFP* vfp = (VFP*)(*(int*)&b1);
cout << "Base vir Tab" << endl;
while (*vfp)
{
(*vfp)();
++vfp;
}
cout << endl;
VFP* vfp2 = (VFP*)(*(int*)&d);
cout << "Derived vir Tab" << endl;
while (*vfp2)
{
(*vfp2)();
++vfp2;
}
}
int main()
{
cout << sizeof(Derived) << endl;
Printvfp();
system("pause");
return 0;
}
在这个程序中,我们主要看一下派生类Derived的大小,
虚继承的特点就是把基类放在了下面,但我们也可以看到派生类的大小是20,我们姑且记住这个大小,再来看看下面这个程序。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
public:
int _data1;
};
class Derived :virtual public Base
{
public:
Derived()
{}
~Derived()
{}
virtual void FunTest1()
{
cout << "Derived::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Derived::FunTest2()" << endl;
}
int _data2;
};
typedef void(*VFP)();
void Printvfp()
{
Derived d;
d._data1 = 1;
d._data2 = 2;
Base& b1 = d;
VFP* vfp = (VFP*)(*(int*)&b1);
cout << "Base vir Tab" << endl;
while (*vfp)
{
(*vfp)();
++vfp;
}
cout << endl;
VFP* vfp2 = (VFP*)(*(int*)&d);
cout << "Derived vir Tab" << endl;
while (*vfp2)
{
(*vfp2)();
++vfp2;
}
}
int main()
{
cout << sizeof(Derived) << endl;
Printvfp();
system("pause");
return 0;
}
在这个程序中,与上一个程序不同的是在派生类中多了构造和析构函数。我们再来分析一下内存存储中有什么不同。
可以看到,派生类的大小多了4个字节,从内存中可以看到这四个字节存了0,这个0其实也没什么作用,因为从底层实现(汇编)没看到这个0做了什么工作,我们只能理解为起分隔作用。现在我们就要讨论什么原因导致加了这个0,很明显,这个程序比上面一个程序就只多了构造和析构函数,所以肯定和这两个函数有关,但要提醒大家的是只要构造和析构函数有一个(当然也包括六个都有)大小就会加4,也就是会加起分隔作用的0.
最后我们再来看一下构造函数在底部到底做了哪些工作?我们来通过反汇编来分析一下:
003937C0 push ebp
003937C1 mov ebp,esp
003937C3 sub esp,0CCh
003937C9 push ebx
003937CA push esi
003937CB push edi
003937CC push ecx
003937CD lea edi,[ebp-0CCh]
003937D3 mov ecx,33h
003937D8 mov eax,0CCCCCCCCh
003937DD rep stos dword ptr es:[edi]
003937DF pop ecx
003937E0 mov dword ptr [this],ecx
003937E3 cmp dword ptr [ebp+8],0
003937E7 je Derived::Derived+3Eh (03937FEh)
003937E9 mov eax,dword ptr [this]
003937EC mov dword ptr [eax+4],39DCA8h
003937F3 mov ecx,dword ptr [this]
003937F6 add ecx,10h
003937F9 call Base::Base (0391230h)
003937FE mov eax,dword ptr [this]
00393801 mov dword ptr [eax],39DC94h
00393807 mov eax,dword ptr [this]
0039380A mov ecx,dword ptr [eax+4]
0039380D mov edx,dword ptr [ecx+4]
00393810 mov eax,dword ptr [this]
00393813 mov dword ptr [eax+edx+4],39DCA0h
0039381B mov eax,dword ptr [this]
0039381E mov ecx,dword ptr [eax+4]
00393821 mov edx,dword ptr [ecx+4]
00393824 sub edx,0Ch
00393827 mov eax,dword ptr [this]
0039382A mov ecx,dword ptr [eax+4]
0039382D mov eax,dword ptr [ecx+4]
00393830 mov ecx,dword ptr [this]
00393833 mov dword ptr [ecx+eax],edx
00393836 mov eax,dword ptr [this]
00393839 pop edi
0039383A pop esi
0039383B pop ebx
0039383C add esp,0CCh
00393842 cmp ebp,esp
00393844 call __RTC_CheckEsp (039134Dh)
00393849 mov esp,ebp
0039384B pop ebp
0039384C ret 4
通过反汇编我们可以总结如下:
1.填写偏移量地址
2.调用基类构造函数
3.填写派生类虚表地址
4.重写基类对象部分虚表地址
5.派生类对象和基类之间用0分隔(有构造或析构)