测试环境 :win10 64位 vs2013
一些概念:
虚函数:类的成员函数前面加上virtual关键字,则此成员函数即为虚函数。
重写:在子类定义了一个与父类完全相同的虚函数,则称子类的虚函数重写了父类的虚函数。
多态:多态就是多种形态,C++的多态分为静态多态和动态多态。静态多态就是重载,因为在编译期间决定调用哪个函数,所以称为静态多态;动态多态是通过继承重写基类的虚函数实现的多态,因为是在运行期间决定调用哪个函数,所以称为动态多态。
1.单继承对象模型
#pragma once
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f1()
{
cout << "Base::f1" << endl;
}
virtual void f2()
{
cout << "Base::f2" << endl;
}
private:
int _a;
};
class Derive :public Base
{
public:
virtual void f1()
{
cout << "Derive::f1" << endl;
}
virtual void f3()
{
cout << "Derive::f3" << endl;
}
private:
int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
cout << "虚表地址:" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d个虚函数地址 :0%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void test1()
{
Base b1;
Derive d1;
int* VTable1 = (int*)(*(int*)&b1);
int* VTable2 = (int*)(*(int*)&d1);
PrintVTable(VTable1);
PrintVTable(VTable2);
}
单继承原理:
对于b1: 在构造时,为所有虚函数创建一个虚表,虚表首地址存在对象b1的首地址中。
对于d1: 在构造时,同样要建立一个虚表,由于继承了父类;所以首先构造父类内成员,然后再构造d1内其他成员,在构造父类后,由于存在重写f1。所以将d1中f1覆盖到父类f1位置,以下为测试。
内存分布:
运行代码发现在监视窗口并没有显示d1完整的虚函数地址,打开内存窗口,输入d1的虚表地址。
上述数据基本符合预期,为了更直观,将上述虚函数地址打印,如下:
现在在Base里添加两个Display()函数,一个传参,一个不传,使他们构成重载。
void test2()
{
Derive d2;
Base& b2 = d2;
b2.f1();
b2.Display();
b2.Display(1);
}
查看反汇编,明显得出动态多态与静态在寻址时极为不同的方式
b2.f1(); ;动态多态,虚表寻找地址
mov eax,dword ptr [b2] ;b2地址给eax
mov edx,dword ptr [eax] ;将eax指向的内容给edx
mov esi,esp
mov ecx,dword ptr [b2] ;b2地址给eax
mov eax,dword ptr [edx] ;将edx内容给eax
call eax
cmp esi,esp
call __RTC_CheckEsp (01B1361h)
b2.Display(); ;静态多态,编译时确定地址
mov ecx,dword ptr [b2]
call Base::Display (01B124Eh)
b2.Display(1);
push 1
b2.Display(1);
mov ecx,dword ptr [b2]
call Base::Display (01B10E1h)
2.多重继承对象模型
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void f1()
{
cout<<"Base1::f1"<<endl;
}
virtual void f2()
{
cout<<"Base1::f2"<<endl;
}
private:
int _b1;
};
class Base2
{
public:
virtual void f1()
{
cout<<"Base2::f1"<<endl;
}
virtual void f2()
{
cout<<"Base2::f2"<<endl;
}
private:
int _b2;
};
class Derive: public Base2,public Base1
{
public:
virtual void f1()
{
cout<<"Derive::f1"<<endl;
}
virtual void f3()
{
cout<<"Derive::f3"<<endl;
}
private:
int _d1;
};
typedef void(*FUNC)();
void PrintVTable(int* VTable)
{
cout << "虚表地址:" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf("第%d个虚函数地址 :0x%x,->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();
}
cout << endl;
}
void test1()
{
Derive d;
PrintVTable((int*)(*((int*)&d)));
PrintVTable((int*)(*((int*)((char*)&d + sizeof(Base1)))));
}
多重继承原理:
在重多继承过程中,子类的成员函数重写(上例f1),如果两个父类同时包含重写的函数,将子类的其他虚函数存在优先继承的虚函数表中(上例先继承Base2)。
内存分布:
打开监视窗口仍然发现不能显示全部虚函数的地址,如下:
然后我们可以打印出虚函数地址,如下:
于是我们得到多重继承模型:
3.菱形虚拟继承
#include <iostream>
using namespace std;
class A
{
public:
virtual void f1()
{}
int _a;
};
class B : virtual public A
{
public:
virtual void f1()
{}
virtual void f2()
{
cout << "D::f2()" << endl;
}
int _b;
};
class C : virtual public A
{
public:
virtual void f1()
{}
virtual void f2()
{
cout << "D::f2()" << endl;
}
int _c;
};
class D : public B, public C
{
public:
virtual void f1()
{
cout << "D::f1()" << endl;
}
virtual void f3()
{
cout << "D::f3()" << endl;
}
int _d;
};
void test1()
{
D d1;
d1._a = 1;
d1._b = 2;
d1._c = 3;
d1._d = 4;
PrintVTable((int*)(*(int*)&d1));
PrintVTable((int*)(*((int*)&d1 + 3))); //+3是因为用sizeof计算崩溃
PrintVTable((int*)(*((int*)&d1 + 7))); //所以对照内存表人工计算
}
通过查看内存分配和打印每个虚表内函数地址,画出如下d1模型图,由于虚继承将A构造的对象,放置在了最下面,然后有一虚基表指向此处,以后可以访问。