关于多态和虚函数的实现原理

什么叫多态?

简单点说法就说同一个接口,有多种不同的实现方式。

C++ 多态是利用虚函数来实现的, 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

什么是虚函数?

某基类中声明为virtual并在一个或多个派生类中重新定义的成员函数叫做虚函数;

虚函数有什么作用?

虚函数的作用是实现动态联编,也就是在函数运行阶段动态的选择合适的成员函数;

在定义了虚函数后,可以在派生类对虚函数进行重写,从而实现统一的接口,不同的执行过程。


虚函数的实现是由两个部分组成的,虚函数指针和虚函数表实现。

虚函数表指针vptr,每一个类的对象都有一个虚函数表指针,该指针指向类的虚函数表的位置。为了实现多态,当一个对象调用某个虚函数时,实际上是根据该虚函数指针vptr所指向的虚函数表vtable里找到相应的函数指针并调用之。所有的, 同一个类, 共用同一份虚函数表。

带有虚函数的类,编译器会为其额外分配一个虚函数表,里面记录的使虚函数的地址,当此类被继承时,子类如果也写了虚函数就在子类的虚函数表中将父类的函数地址覆盖,否则继承父类的虚函数地址。


1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

3)基类和派生类是各有各的表,也就是说他们的物理地址是分开的

多重继承(无虚函数覆盖)

对于子类实例中的虚函数表,是下面这个样子:


1) 每个父类都有自己的虚表。2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

多重继承(有虚函数覆盖)

下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()


任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法。

如 

Base1 *b1 =newDerive();

b1->f1();//编译出错

(1)构造函数不能为虚函数:

1.虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚的,就需要通过 虚指针执行那个虚函数表(编译期间生成属于类)来调用,可是对象还没有实例化,也就是内存空间还没有,就没有虚指针,所以构造函数不能是虚函数。

2.虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

3、构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型。

(2)析构函数可以是虚函数:

当使用父类指针或者引用调用子类,最好将父类的析构函数声明为虚函数,避免内存泄漏。

例:

子类B继承自父类A:A *p = new B; delete p;

如果A的析构函数不是虚函数:delete p; 仅调用A的析构函数,只释放了B对象中的A部分,派生出的新的部分未释放掉。

如果A的析构函数是虚函数:delete p; 将会先调用B的析构函数,再调用A的析构函数,释放B对象的所有空间。B *p = new B; delete p; 也是先调用B的析构函数,再调用A的析构函数。

派生类的析构函数在执行完后,会自动执行基类的析构函数

为什么派生类会会自动执行基类的析构函数?

同时对于所有派生类的析构函数,编译器都会插入调用直接基类的析构函数的代码。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容