C++_虚函数调用的工作原理(基于虚函数多态的机制)

在运行状态下进行称之为动态。

1.虚函数表
(1)包含虚函数的类
class B {

    virtual int f1 (void);
    virtual void f2 (int);
    virtual int f3 (int);
};

编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的下标。

             +---------+---------+---------+

vptr1-> |   B::f1   |   B::f2   |   B::f3  |

             +---------+---------+---------+

                 0             1              2

除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址,

该变量被称为虚函数表指针,简称虚指针(vptr)。例如:

B* pb = new B;
pb->f3 (12);
被编译为
pb->vptr[2] (pb, 12); // B::f3       参数pb是this指针

注意:虚表是一个类一张,而不是一个对象一张,同一个类的多个对象,通过各自的虚指针,共享同一张虚表。

           +---------+---------+---------+

vptr-> | vptr1  |   vptr2 |   vptr3 |

           +---------+---------+---------+

(2)继承自B的子类
class D : public B {
    int f1 (void);
    int f3 (int);
    virtual void f4 (void);
};
子类覆盖了基类的f1和f3,继承了基类的f2,增加了自己的f4,编译器同样会为子类生成一张专属于它的虚表。

                          +---------+---------+---------+---------+

vptr(子类)->|   D::f1   |   B::f2   |   D::f3  |  D::f4  |

                          +---------+---------+---------+---------+

                                  0             1            2           3

指向子类虚表的虚指针就存放在子类对象的基类子对象中。例如:

B* pb = new D;
pb->f3 (12);
被编译为
pb->vptr[2] (pb, 12); // D::f3
这就是多态的工作原理。

用一个函数做一个测试:

class A{
public: 
    A():m_ch('A'){}
    virtual void foo() {
        cout << m_ch << "::foo()" << endl ;
    }
    virtual void bar(){
        cout << m_ch << "::bar()" << endl ;
    }
private:
    char m_ch ;
} ;
class B:public A{
public:
    B():m_ch('B'){}
    void foo(){
        cout << "B::foo()" <<endl ;
    }
private:
    char m_ch ; 
} ;
int main(){
    A a ;
    void(**vptr_a)(A*) = *(void(***)(A*))&a ;
    cout << (void *)vptr_a <<endl ;                                 //0x8048bb0
    cout << "foo():"<<(void *)vptr_a[0] <<endl ;            //foo():0x8048992
    cout << "bar():" <<(void *)vptr_a[1] <<endl ;           //bar():0x80489d4
    vptr_a[0](&a) ;                                                              //A::foo()
    vptr_a[1](&a) ;                                                              //A::bar()
    cout << "-----------------------------------------" <<endl ;
    B b ;
    void(**vptr_b)(B*) = *(void(***)(B*))&b ;                
    cout << (void *)vptr_b <<endl ;                                 //0x8048ba0
    cout << "foo():"<<(void *)vptr_b[0] <<endl ;            //foo():0x8048a3a 
    cout << "bar():" <<(void *)vptr_b[1] <<endl ;           //bar():0x80489d4
    vptr_b[0](&b) ;                                                              //B::foo()
    vptr_b[1](&b) ;                                                              //A::bar()
}

上述程序说明了虚函数表是真实存在的:

(1)void(**vptr_a)(A*) = *(void(***)(A*))&a ;建立一个vptr_a的虚函数表,如下图:

对象a的起始地址就是虚指针起始的所指向的地址。

(2)通过基类和子类的虚函数指针指向的地址对比可以看出,基类和子类的虚函数表不是一个

(3)所谓的多态,就是子类中的函数原型(返回类型+函数名+形参表+常属性)相同的基类虚函数进行一个替换,编译器是通过this指针来区分对象。

2.动态绑定

当编译器“看到”通过指针或者引用调用基类中的虚函数时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该调用语句,这段代码在运行时被执行,完成如下操作:
1)根据调用指针或引用的目标对象找到其内部的虚表指针;
2)根据虚表指针找到其所指向的虚函数表;
3)根据虚函数名和函数指针在虚函数表中的索引,找到所调用虚函数的入口地址;
4)在完成函数调用的准备工作以后,直接跳转到虚函数入口地址处顺序执行函数体指令序列,直到从函数中返回。
3.动态绑定对性能的影响
1)虚函数表和虚指针的存在势必要增加内存空间的开销。
2)和普通函数调用相比,虚函数调用要多出一个步骤,增加运行时间的开销。
3)动态绑定会妨碍编译器通过内联优化代码,虚函数不能内联。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值