对虚函数的理解

 1.     

     从基类指针或基类引用获得派生类的行为的最好方法是通过虚函数机制。

     虚函数是动态绑定的,也就是说,使用指向派生对象的基类指针和引用调用一个virtual函数时,究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型。

    简而言之就是用父类的指针或引用 指向 其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”。

    

2. 构造函数不能是虚函数,另外,析构函数允许为虚并且往往是必要的。

     在构造函数或析构函数中调用虚函数,实际执行的是该类的对应函数,而不是子类的对应函数,因为子类非基类的部分还没有构造好,多态是被disable的。 

 

3.虚函数的实现原理:

       编译器会为每个含有虚函数的类添加一个隐藏的数据成员即虚函数表指针(vptr),在构造函数内插入隐藏的将虚函数表指针初始化为虚函数首地址的代码,创建一个虚函数表(vtble)。

       一般虚函数表指针放在对象首地址的前4字节处,大部分编译器都是这样做的,这主要是为了保证取到虚函数表的有最高性能。
虚函数表将被该类的所有对象共享。而且对于虚函数表,实质上是一块连续的内存,每4字节的内存单元存放了一个虚函数的入口地址,而虚函数的地址的排列顺序是依据虚函数在类中的声明顺序而定的。
       在实例化含有虚函数的类对象时,会调用构造函数完成虚函数表指针的初始化。在虚函数被调用时,
通过vptr间接访问vtble,在vtble中找到对应的虚函数地址并调用执行。

 

4.虚函数表

   虚函数表放在定义这个虚函数的导出类的模块的常量段中。一个含有虚函数的类只有一个虚函数表,也就是说从该类实例化出来的对象共享一个虚函数表

   

5. 虚函数地址的获取

由于虚表指针存放在类对象的前4个字节中,我们首先需要将类对象的首地址转化成int型指针,并通过这个int型指针获得前4个字节的内容,这个内容就是虚表的地址。接着我们将这个虚表的地址再转化成int型指针,并通过这个int型指针获得虚表的前4个字节的内容,这个内容就是虚表的第一项的值,也就是第一个虚函数的地址。

 

//假设虚函数指针类型为Fun;

 

Derieved d;

(int*)(&d)             取vptr的地址
*(int*)(&d)            取vptr的值,即vtble的地址
(int*)*(int*)(&d)    取vtble的值,即vtble首元素的地址,注:vtble是一个虚函数指针数组
(Fun)*((int*)*(int*)(&d) +0)      取vtbl数组的第一个元素的值,即Derived中第一个虚函数的地址
 (Fun)*((int*)*(int*)(&d) +1)     取vtbl数组的第二个元素的值

或者

*(Fun*)((int*)*(int*)(&d) +0)    取vtbl数组的第一个元素,即Derived中第一个虚函数的地址

*(Fun*)((int*)*(int*)(&d) +1)    取vtbl数组的第二个元素


6.

#include <iostream>

using namespace std;

class B   
{    
public:    
    virtual void fun()      
    {     
        cout << "base fun called";     
    };    
};  

class D : public B    
{    
private:   
    virtual void fun()      
    {     
        cout << "driver fun called";    
    };    
};  

int main(int argc, char* argv[])   
{       
    B* p = new D();    
    p->fun();    
    return 0;    
}  
运行时会输出 driver fun called

从这个实验,可以更深入的了解虚拟函数编译时的一些特征:
在编译虚拟函数调用的时候,例如p->fun(); 只是按其静态类型来处理的, 在这里p的类型就是B,不会考虑其实际指向的类型(动态类型)。
    也就是说,碰到p->fun();编译器就当作调用B的fun来进行相应的检查和处理。
因为在B里fun是public的,所以这里在“访问控制检查”这一关就完全可以通过了。
然后就会转换成(*p->vptr[1])(p)这样的方式处理, p实际指向的动态类型是D,
    所以p作为参数传给fun后(类的非静态成员函数都会编译加一个指针参数,指向调用该函数的对象,我们平常用的this就是该指针的值), 实际运行时p->vptr[1]则获取到的是D::fun()的地址,也就调用了该函数, 这也就是动态运行的机理。


为了进一步的实验,可以将B里的fun改为private的,D里的改为public的,则编译就会出错。
C++的注意条款中有一条" 绝不重新定义继承而来的缺省参数值" 
(Effective C++ Item37, never redefine a function's inherited default parameter value) 也是同样的道理。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值