cpp面向对象的又一個重要特性, 多态(polymorphism)的vptr指针调用实现,以及为什么int**到了64位系统就会失效

本文详细解释了C++中多态的实现机制,包括继承、虚函数、虚函数表和动态绑定,通过实例展示了如何使用虚指针实现多态功能,以及为何不能直接使用int类型表示虚函数指针。

目录

写在前面

具体实现

为什么不可以是int类型**?

总结

致谢


写在前面:

今天学习到了继承中的多态,这个知识点我已經学了不下10次了,每一次几乎都会讲到这个知识点,去年我在上武汉理工大学汪洋博士的Openfoam Programming 2306的时候手动实现了一次,但是其实当时有一些小bug并没有解决,理解了大概意思就过了(那门课程的节奏有点顶)。今天正好赶到Rock老师讲到这里了,我之前的记忆也差不多还给汪博了,于是我决定这一次直接彻底把他搞透(学习之前还是有些抵触的,差点暂时跳过),于是有了这一篇文章(除了搞懂了这个基本上今天啥也没干)。网上也有很多博主讲过这个东西了,老八股文了,大佬们多见谅。


具体实现:

  • 多态的实现:继承机制(inheritance)+虚函数机制(virtual function), 虚函数只需要加载在父类中,但是在子类中也给出声明可以增加代码的可读性。

  • 为什么我们需要多态?

    • 提高代码的可重用性,利用多态可以实现不同派生类继承于相同基类,这样避免了大量的重复代码,使得代码更加简洁。

    • 实现一致的接口,对于不同类型的派生类我们可以通过多态分别定义其实现,这样用户不需要过度关心接口的区分,这也提升了代码内在实现的隐秘性。

    • 提高代码可扩展性,如果使用多态,那么想实现一个新的派生类,只需要修改部分接口即可对代码进行相应的扩展。

    • 动态绑定,运行时多态机制使得我们定义一个函数不需要给各种子类分别定义,编译器会自动查询虚表进而实现不同类型对象调用各自内部定义的函数。

    • 说人话其实就是人们常说的父类引用或指针为形参,传入子类的对象之后,在函数内部调用的所有方法都是子类重写的方法(如果有继承关系且声明为虚函数并在子类中重写)

  • 多态虚函数的原理:虚函数表

    • 说完了原因,那么这个运行时多态的机制到底是什么呢?其实我们作为程序员只需要了解他的使用就可以了,但是对于面试来说虚函数以及运行时多态的原理是绕不过去的一个话题。为此就引出了我们运行时多态的始作俑者:虚函数表和虚函数。

    • 虚函数:

      • 虚函数指在一个函数的基类中被声明为virtual的函数,这个函数会在派生类中被重写。当基类指针或引用调用这个函数时,编译器会根据对象的不同匹配相应的函数定义,实现多态。

    • 虚函数表(vtable):

      • 为了实现运行时多态,编译器使用虚函数表的结构,他为每一个类创建一个虚函数表,这个表是一组函数指针,每一个指针指向一个函数的实现。

      • 当一个类对象被创建时,其内部会包涵一个虚指针(vptr),指向其对应类的虚函数表,虚函数表中包含了类内所有的虚函数的地址。

      • 当调用一个虚函数,编译器会调用这个对象虚指针指向的类当中定义的相应的虚函数,编译器也可以通过基类的指针或者引用直接访问派生类中重写的虚函数版本,这种机制使得函数的行为可以根据对象的实际类型而变化也是运行时多态的原理。

    • 虚函数指针(vptr):

      • 每一个包含虚函数的类的对象都会包含一个隐藏成员,虚函数指针,通常情况下vptr是对象内存布局中的第一个对象,即vptr的地址通常就是对象的地址

    • 为此,借用rock老师的思路我画了一个虚函数表的示意图,如上面提到的虚函数表是编译时为每个含有虚函数的类创建的,而每一个新生成的对象内部包含一个虚指针,他指向了对象创建时所属类的虚函数表,如此一来实现了多态。

    • 我们再来总结一下, 一个对象生成时,(在大多数编译器中)他的内存分配的第一个位置保存的隐藏成员就是vptr,这个vptr指向了他的所属类别的vtbl,他的vtbl+偏移量指向的就是相应的虚函数: 即 vptr = &obj >> vtbl = *vptr >> func = vtbl[offset] >> func = (*&obj)[offset], 所以对于func来说,vtbl是一级指针*,那么vptr就是二级指针**(也就是对象的地址),注意偏移量不等于字节数,不能直接在vtbl指针的基础上+1,2,3。

    • 下面我们举一个例子,就一个基类和Base一个派生类Derived,基类里面有三个虚函数,子类中实现了前两个,第三个不实现。然后实现就是直接打印出相应的函数位置,再加上一个普通成员函数,两个成员变量以及一个静态成员变量,由此用于说明类对象的内存分布。

    • 定义类的代码如下:

      #include<iostream>
       #include<cstdint>
       ​
       ​
       class Base{
       public:
           //vptr = 8 in 64 bit os
           //vptr = 4 in 32 bit os
           virtual void func1(){
               std::cout << "in Base::func1" << std::endl;
           }
           virtual void func2(){
               std::cout << "in Base::func
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值