博客是从51cto迁移过来的,图片显示有问题,原文链接:https://blog.51cto.com/haipi/1753646
c++中多态的实现
我们都知道,c++中的多态是在虚函数的基础上实现的,用指向派生类的基类指针调用派生类(或基类)中自己的成员函数。那么,具体是怎么实现的呢?
其实它是通过虚函数表来实现的,虚函数表是保存虚函数地址的一张表,若一个类中有虚函数,当程序运行时,编译器通过在虚函数表中查找相应的虚函数的地址来调用该函数。
对象的继承有如下几类:
1.单一继承
2.多重继承
3.重复继承(钻石继承)
4.虚继承
下面我们分别来看一下各种继承的内存布局:
单一继承
单一继承的结构
运行下面这段程序:
#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B
{
public:
B() :_b(0){}
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
private:
int _b;
};
class D:public B
{
public:
D() :_d(1){}
void fun1()
{
cout << "D::fun1()" << endl;
}
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
private:
int _d;
};
void PrintVT(int b)//根据虚函数表中的函数地址调用虚函数
{
PFUN pfun = NULL;
int *ptr = (int *)b;
int i = 0;
while (ptr[i])
{
pfun = (PFUN)ptr[i];//将虚函数的地址转换为函数指针
pfun();//用函数指针调用函数
i++;
}
}
int main()
{
D d;
PrintVT(*(int *)&d);//取出虚函数表的地址
getchar();
return 0;
}
我们在内存窗口可以看到:(vs2013)
我们可以在内存中看到对象d在内存中的存储结构如上图所示,根据对象在内存中的的存储结构我们可以取出内存中虚函数表的地址,从而拿到虚函数表中的虚函数地址,通过函数指针可以调用各个虚函数。这样我们就可以知道虚函数表中所存是哪些虚函数的地址。
上面程序的功能是调用虚函数表中函数的地址所对应的函数,程序运行结果如下:
单一继承的内存布局是:
单一继承对象模型
1)对象地址的最前面放的是虚函数表的地址,然后根据继承的顺序依次放置成员变量的地址。
2)虚表里按继承顺序先放置派生类从基类继承来的虚函数的地址,然后是派生类自己的虚函数的地址。
3)满足覆盖条件的派生类的虚函数地址覆盖了基类虚函数的地址。
覆盖:
1)不同作用域(基类和派生类中)
2)函数名相同,参数列表相同,返回值类型相同(协变除外)
3)为虚函数(virtual)
注:协变是指基类虚函数返回值为基类类型的指针,派生类虚函数的返回值为派生类类型的指针
多重继承
多重继承结构
运行下面这段程序:
#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B1
{
public:
B1() :_b1(1){}
virtual void fun1()
{
cout << "B1::fun1()" << endl;
}
virtual void fun2()
{
cout << "B1::fun2()" << endl;
}
private:
int _b1;
};
class B2
{
public:
B2() :_b2(2){}
virtual void fun1()
{
cout << "B2::fun1()" << endl;
}
virtual void fun2()
{
cout << "B2::fun2()" << endl;
}
private:
int _b2;
};
class D:public B1,public B2
{
public:
D() :_d(3){}
void fun1()
{
cout << "D::fun1()" << endl;
}
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
private:
int _d;
};
void PrintVT(int b)
{
PFUN pfun = NULL;
int *ptr = (int *)b;
int i = 0;
while (ptr[i])//虚函数表结束标志为0,当ptr[i]为0时,循环结束
{
pfun = (PFUN)ptr[i];
pfun();
i++;
}
}
//调用虚表中函数的方法与单一继承一致
int main()
{
D d;
PrintVT(*(int *)&d);//拿到第一个虚表的地址
cout << "---------" << endl;
PrintVT(*((int *)&d+2));//拿到第二个虚表的地址
getchar();
return 0;
}
我们在内存窗口可以看到:(vc++6.0)
程序运行结果如下:
上面是第一个虚表中虚函数的调用,下面是第二个。
根据内存分布,多重继承的内存布局为:
多重继承的对象模型
1)对象地址中按继承顺序分别放置派生类继承下来的基类的虚函数表的地址和它的成员变量,派生类的成员变量放在最后
2)派生类的虚函数的地址在第一个虚函数表中
3)派生类的虚函数地址覆盖基类虚函数地址(按以上单一继承的条件)
重复继承(钻石继承)
重复继承结构
运行下面这段程序:
#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B1
{
public:
B1() :_b1(1){}
virtual void fun1()
{
cout << "B1::fun1()" << endl;
}
virtual void funB()
{
cout << "B1::funB()" << endl;
}
private:
int _b1;
};
class B2:public B1
{
public:
B2() :_b2(2){}
virtual void fun1()
{
cout << "B2::fun1()" << endl;
}
virtual void fun2()
{
cout << "B2::fun2()" << endl;
}
virtual void funB2()
{
cout << "B2::funB2()" << endl;
}
private:
int _b2;
};
class B3:public B1
{
public:
B3() :_b3(3){}
virtual void fun1()
{
cout << "B3::fun1()" << endl;
}
virtual void fun2()
{
cout << "B3::fun2()" << endl;
}
virtual void funB3()
{
cout << "B3::funB3()" << endl;
}
private:
int _b3;
};
class D:public B2,public B3
{
public:
D() :_d(4){}
void fun1()
{
cout << "D::fun1()" << endl;
}
virtual void fun2()
{
cout << "D::fun2()" << endl;
}
virtual void funD()
{
cout << "D::funD()" << endl;
}
private:
int _d;
};
void PrintVT(int b)
{
PFUN pfun = NULL;
int *ptr = (int *)b;
int i = 0;
while (ptr[i])
{
pfun = (PFUN)ptr[i];
pfun();
i++;
}
}
int main()
{
D d;
PrintVT(*(int *)&d);
cout << "---------" << endl;
PrintVT(*((int *)&d+3));
getchar();
return 0;
}
我们在内存窗口可以看到:(vc++6.0)
程序运行结果:
根据内存分布,重复继承的内存布局为:
重复继承的对象模型
1)对象的地址按照直接基类继承顺序分别放置虚表地址和数据成员(继承自基类的数据成员在前),然后放自己的数据成员。
2)虚函数表中先放置间接基类的虚函数地址,然后是直接基类的虚函数地址
3)派生类的虚函数放在第一个虚表中
4)基类中的虚函数被派生类中虚函数覆盖
这种继承方式存储了两份来自间接基类的数据成员,程序访问该成员时导致二义性。因此我们通常用虚继承。
虚继承
虚继承结构
运行下面这段程序,我们可以在内存中看到对象d在内存中的存储结构:
#include<iostream>
using namespace std;
typedef void(*PFUN)();
class B
{
public:
B() :_b(1){}
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void funB()
{
cout << "B::funB()" << endl;
}
private:
int _b;
};
class B1:virtual public B
{
public:
B1() :_b1(2){}
virtual void fun1()
{
cout << "B1::fun1()" << endl;
}
virtual void fun2()
{
cout << "B1::fun2()" << endl;
}
virtual void funB1()
{
cout << "B1::funB1()" << endl;
}
private:
int _b1;
};
class B2:virtual public B
{
public:
B2() :_b2(3){}
virtual void fun1()
{
cout << "B2::fun1()" << endl;
}
virtual void fun2()
{
cout << "B2::fun2()" << endl;
}
virtual void funB2()
{
cout << "B2::funB2()" << endl;
}
private:
int _b2;
};
class D:public B1,public B2
{
public:
D() :_d(4){}
void fun1()
{
cout << "D::fun1()" << endl;
}
virtual void fun2()
{
cout << "D::fun2()" << endl;
}
virtual void funD()
{
cout << "D::funD()" << endl;
}
private:
int _d;
};
void PrintVT(int b)
{
PFUN pfun = NULL;
int *ptr = (int *)b;
int i = 0;
while (ptr[i])
{
pfun = (PFUN)ptr[i];
pfun();
i++;
}
}
int main()
{
D d;
PrintVT(*(int *)&d);
cout << "---------" << endl;
PrintVT(*((int *)&d+3));
cout << "---------" << endl;
PrintVT(*((int *)&d+7));
getchar();
return 0;
}
我们在内存窗口可以看到:(vc++6.0)
程序运行结果:
根据内存分布,虚继承的内存布局为:
虚继承的对象模型
1)虚继承的对象地址处首先放置的是该类直接的第一个基类的虚函数表的地址,然后存储一个地址,地址中存储能够找到虚基类的虚表地址的偏移量(其中-4可能是一个标志,以0结束)然后存储继承自该类的数据成员
2)派生类的虚函数保存在第一个直接基类的虚函数表中
3)然后存储第二个继承自直接基类的虚函数表地址和偏移量地址及数据成员
4)最后存储派生类自己的数据成员,以0结束
5)存储虚基类的虚函数表的地址,虚基类的虚函数表地址可有前面保存的偏移量找到。
6)然后存储继承自虚基类的数据成员,我们可以看出虚继承这种存储模型只存储一份来自虚基类的数据成员,因此避免了二义性。