多态是C++的三大重大特性之一,简而言之,它即是在运行时才确定到底应该执行父类还是子类的虚函数。
那么它是如何实现的喃?说到其实现原理,我们必须谈到两点:动态(推迟)绑定技术和虚表。
我们先来讲动态绑定,对类的普通函数而言,若该类对象调用了该类某函数,则在程序编译时便确定了应该调用哪个函数。而当我们在类成员函数前加上virtual之后,情况则发生了改变,程序编译时不再对带有virtual关键字的函数调用进行绑定,而是在程序运行时再根据虚表中的函数地址进行调用。
在动态绑定的时候,我们已经涉及到了虚表。当某个类包含一个带有virtual关键字的方法时,该类的对象在被初始化时(构造函数中)则会生成一张虚表和一个虚表指针。虚表中包含了该类虚函数的入口指针,虚表指针则指向虚表。需要说明的是,虚表是可以继承的,即派生类将继承基类虚表中的所有元素。若派生类重写了基类中的某个虚函数,则在派生类对象构造时将自己虚表中该虚函数的地址替换成自己类中的函数入口。
举例如下,若某类Base定义:
class Base
{
Base(){}
void func(){}
virtual void func1(){}
virtual void func2(){}
}
Base *base;
则在base的虚表中有两项元素,分别指向func1()和func2()的入口地址。假设派生类SubClass公共继承自Base类,如下:
class SubClass : public Base
{
SubClass(){}
virtual void func3(){}
void func1(){}//基类中func1()已有virtual关键字,派生类的同名同参函数默认为虚函数,无需再显示定义virtual
}
SubClass subClass;
则在subClass的虚表中,也有三项元素,其一指向Base::func2(),另一指向SubClass::func1(),最后一个指向SubClass::func3()。具体情况是:执行SubClass()时,首先执行的是基类的Base(),此时构建了虚表,该虚表中有两项元素,分别是Base::func1()和Base::func2()。基类构造函数执行完成之后,执行SubClass(),此时发现派生类重写了func1(),则将虚表中func1()的入口地址替换成SubClass::func1(),然后再加上自己新加的虚函数入口SubClass::func3()。则完成了自己的虚表,此时虚表中不再有Base::func1()。
在程序执行虚函数的时候,再根据当前对象的类型,找到其虚表,动态地查询虚函数的入口。
当Base *base = new SubClass(); 执行base->func1()时,base指向的对象是SubClass,则其虚表中func1()的地址是SubClass::func1(),因而执行的是SubClass中的func1()。
另外,刚才我们提到了重写(Override,覆盖),它和重载(Overload)是有天壤的区别的。重写是多态的实现方式,而重载仅仅是C++提供的可以使用相同函数名(但形参个数,或者形参类型不一样)的机制而已。