多态(polymorphism)是C++语言的一大特性,该词最早出自于希腊语,意为多种状态,而C++中的多态是实现接口复用的一种手段,即在保证接口不变的情况下,根据作用对象的不同而产生不同的运行结果—。
C++中实现多态有两个必要的条件:
- 派生类重写基类的虚函数;
- 基类的指针或引用指向派生类的对象。
对于第二点很好理解,而第一点中“重写虚函数是什么意思呢?”,下面详细探讨这一点:
虚函数
还记得我们之前的虚拟继承那篇博客吗?使用 virtual
关键字修饰被继承的类,则构成了虚拟继承,事实上,虚函数的实现机制和虚拟继承有着异曲同工之妙。声明方法如下:
virtual void f()
{...}
上面的代码定义了一个虚函数,那么虚函数和普通的函数有什么不同呢?
class Base{
public:
void f()
{}
private:
int m_i;
};
对于上面的 Base
类, 我们用 sizeof
求大小的结果毫无疑问是 4,但是如果将函数f
定义为虚函数的话,它的大小会发生变化,如下:
为什么类中有了虚函数之后,类的大小会增加 4 个字节呢?
因为具有虚成员函数的类中,会增加一个虚表指针,该指针指向一个虚函数表,该表中记录了虚函数的地址。看看下面这段代码:
//...
class Base{
public:
virtual void f()
{
cout << "Base::f()" << endl;
}
virtual void g()
{
cout << "Base::g()" << endl;
}
private:
int m_i;
};
typedef void(*Fun)(void);
//...
Base b;
Fun pf[2];
pf[0] = (Fun)*(int*)(*(int*)&b);
pf[1] = (Fun)*((int*)(*(int*)&b)+1);
pf[0]();
pf[1]();
//...
我们通过虚表指针取得虚函数表,再通过虚函数表中的函数地址来调用虚函数,下面详细讨论:
虚函数表
C++中的虚函数是通过虚函数表来实现的,这张表中记录了类的虚函数地址,继承体系中,出现虚函数覆盖的场景时,这张表就显得尤为重要,我们先看在无继承体系下,对象的内存布局:
在 VS 下,通过内存监视窗口可以看到,对象的最前面是一个指向虚表的指针,接着是成员变量。将虚表指针置于对象实例的最前位置,是为了保证取虚函数具有最高的性能。
多重继承体系下对象模型
看下面这段代码:
class Base1{
public:
virtual void f(){
cout << "Base1::f()" << endl;
}
virtual void g(){
cout << "Base1::g()" << endl;
}
//...
};
class Base2{
public:
virtual void f(){
cout << "Base2::f()" << endl;
}
virtual void g(){
cout << "Base2::g()" << endl;
}
//...
};
class Derive : public Base1, public Base2{
public:
virtual void f(){
cout << "Derive::f()" << endl;
}
virtual void g1(){
cout << "Derive::g1()" << endl;
}
//...
};
派生类 Dervice
继承了两个基类 Base1
、Base2
。两个基类各有三个虚函数函数,派生类中重写了第一个虚函数 f()
,下面看看他的内存布局:
可以看到:在对象 d 的实例最前面是两个虚表指针,分别指向所继承的两个基类的虚表,我们可以将两个虚表指针看成一个整体,则该整体是一个指针数组,该指针有指向一个指针数组,后者的指针指向该类虚函数。所以,我们可以像下面这样调用虚函数:
Derive d;
pf Fun = NULL;
int **pVtab = (int**)&d;
Fun = (pf)pVtab[0][0];
Fun();
Fun = (pf)pVtab[0][1];
Fun();
Fun = (pf)pVtab[0][1];
Fun();
Fun = (pf)pVtab[1][0];
Fun();
Fun = (pf)pVtab[1][1];
Fun();
运行结果如下:
当然我们也可以像最开始的那种方法——先通过虚表指针取虚表,在取虚函数,最后通过类型强转调用它。
而且也可以看到,基类中被重写的虚函数位置已经被替换为派生类的虚函数指针,这样,我们就可以通过指向派生类的指针或引用取=去调用派生类的函数了。正是这一点,使得 C++ 具有多态的特性。
通过虚表访问私有函数
类的私有虚函数也会被记录在虚表中,因此我们可以在类的外部通过虚表调到私有函数,这也是虚表所带来的弊端。
class Base1{
private:
virtual void f(){
cout << "Base1::f()" << endl;
}
virtual void g(){
cout << "Base1::g()" << endl;
}
};
可以看到,类的私有函数在类外被访问到了。
——完!
【作者:果冻 http://blog.csdn.net/jelly_9】