什么是多态?
多态是C++三大特性之一,它可以简单的概括为“一个接口,多种方法”,程序在运行时才能通过基类指针指向的对象的类型来决定调用那个函数,今天我们就要谈谈多态的底层的实现。
虚函数
C++多态是通过虚函数来实现的,所谓的虚函数,就是在类的成员函数返回值类型前面加上virtual关键字来定义的,在C++中允许派生类对基类的虚函数进行重写,一旦派生类对基类的虚函数进行重写,那么在派生类中,基类的虚函数将会被覆盖,如果我们想通过派生类的对象来调用基类的虚函数有两种方法,一种是通过访问限定符来调用;另一种方法是动态绑定
动态绑定
多态与非多态的实质性区别就是所调用的函数的地址是静态绑定(早绑定)还是动态绑定(晚绑定),如果函数调用的地址在编译期间就可以被确定,并产生相关代码,那么就是静态绑定;如果函数调用的地址是在运行时决定的,那么就属于动态绑定。
继承与转换
在C++中,封装是为了使代码啊模块化,继承是为了扩展已经存在的代码,两个特性都是为了达到代码重用的目的;而多态则是为了让接口能够被重用,也就是说,不论传递过来的是那个类的对象,函数都能通过同一个接口调用到适应各自对象的方法,实现这个技术最常用的方法就是定义一个基类的指针,利用指针指向任意一个子类的对象,调用相应的虚函数。如下图:
虚函数的重写
* 虚函数重写*:当在子类定义了一个与父类完全相同的虚函数时,则称这个子类的函数重写了父类的虚函数(函数名,参数列表,返回值完全相同协变除外)。
协变:当虚函数返回类型是类本身的指针或引用时,上述规则无效。
例如:
class Base
{
public:
virtual Base* Func()
{
cout << "Base::Func()" << endl;
return this;
}
};
class Dervise :public Base
{
public:
virtual Dervise* Func()
{
cout << "Dervise::Func()" << endl;
return this;
}
};
谈完了上面这些实现多态的基础,我们就要开始谈谈多态是如何实现的了
虚表
虚表就是一张虚函数表,每当我们在一个类中定义了虚函数,那么这个类便会产生一个虚函数指针,指向虚函数表的首地址,虚函数的地址在虚函数表中是连续存储的。
对象模型
单继承模型
在单继承对象模型中,如果派生类的虚函数将基类的虚函数进行了重写,那么在虚函数表中,只存在派生类的这个虚函数,而不存在基类的被重写虚函数,从图中我们可以看到,单继承对象模型的第一块内存存放的就是虚函数表指针,在虚函数表指针下面会依次存放基类和派生类的成员变量。
双继承对象模型
在双继承的对象模型中,一共有两个虚函数表指针,第一个虚函数表指针按顺序存放的是继承列表中第一个基类中没有被派生类重写的虚函数地址以及派生类中的没有进行重写的虚函数地址;在第二个虚函数表指针中,只存放了继承列表中第二个基类的虚函数地址,如果派生类对基类的虚函数进行了重写,那么基类的虚函数会被覆盖。
菱形继承对象模型
在菱形继承中,会存在二义性和数据冗余的问题,如上图,_num成员变量是在Grand类中声明的,当Dervise类继承Base1和Base2时,由于Base1和Base2都是由Grand继承而来的,所以_num变量就在Dervise中声明了两次,当创建对象时,Grand中的_num成员就占用了两块内存,这就是菱形继承中存在的数据冗余和二义性问题。
虚继承解决菱形继承中的问题
class Grand
{
public:
virtual void Func()
{
cout << "Grand::Func()" << endl;
}
virtual void Func4()
{
cout << "Grand::Func4()" << endl;
}
protected:
int _num = 1;
};
class Base1:virtual public Grand
{
public:
virtual void Func()
{
cout << "Base1::Func()" << endl;
}
virtual void Func2()
{
cout << "Base1::Func2()" << endl;
}
void Func1()
{
cout << "Base1::Func1()" << endl;
}
protected:
int _num1=10;
};
class Base2 :virtual public Grand
{
public:
virtual void Func()
{
cout << "Base2::Func()" << endl;
}
virtual void Func3()
{
cout << "Base2::Func3()" << endl;
}
protected:
int _num2=30;
};
class Dervise:public Base1,public Base2
{
public:
virtual void Func()
{
cout << "Dervise::Func()" << endl;
}
virtual void Func1()
{
cout << "Dervise::Func1()" << endl;
}
protected:
int _num3=20;
};
总结
多态是C++的特性之一,多态是在虚函数的基础上实现的,而这些的底层都是建立在对象模型的基础上的,
简而言之,我们一个类的对象模型可能会有如下的影响因素:
1)成员变量
2)虚函数(产生虚函数表)
3)单一继承(只继承于一个类)
4)多重继承(继承多个类)
5)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)