虚函数实现原理

来源:互联网 发布:windows的uac 编辑:程序博客网 时间:2024/05/21 17:12

补充:
1、多重继承中,如果多个父类都具有虚函数,那么就会存在多个虚函数表和多个虚函数指针(注意这一点对类的对象的大小的影响),其中一个虚函数表作为主基类,继承类将自己定义的虚函数追加到其后面。

2、调用虚函数是根据其相对虚函数表的首地址的偏移来查找的,只要将基类的虚函数表指针换成子类的基函数指针,自动就会寻找到子类定义的虚函数。

知识点:

1、虚函数的实现方式,是编译器决定的
2、如果类定义了虚函数,编译器会为该类创建一个虚函数表(其实是一个指针数组,数组存放的元素是虚函数地址),该虚函数表并不存储在类里面,编译器为类创建了一个隐藏指针(该指针占用存储空间),该指针指向虚函数表。每个类对象都会有一个隐藏指针。
3、如果声明了虚函数的类被继承,那么它的虚函数表,会被子类复制拷贝(内容一样,物理空间独立),如果该之类覆盖了父类的虚函数,那么虚函数表里面对应的函数指针就会指向,子类的覆盖函数。如果子类新定义了其他虚函数,那么在该虚函数表的后面追加虚函数指针。
4、如果父类指针指向了一个子类对象,那么父类指针会获取子类对象的隐藏的虚函数表指针。当调用一个虚函数后,会通过虚函数表指针找到虚函数,再通过相对偏移,找到对应的虚函数指针,进而找到虚函数。

附:普通函数的调用方式
C++的设计准则之一是:non-static成员函数的调用与非成员函数的调用的效率应该是一致的。选择将函数声明为成员函数是不应该有任何的额外负担的。于是乎编译器在调用成员函数的时候是将其视为非成员函数来调用的。
具体实现是改写成员函数,将成员函数重新书写为一个外部函数,但是这里值得注意的一个技术“mangling”这个是C++里用于处理重载的同名函数的一个技术。使得该函数名字在程序中是独一无二的。
将对象通过指针传入函数内部,该函数中的成员变量使用this指针来进行间接存取。例如:
(这也就是,为什么我们讲非静态成员函数都隐含一个this指针,this用来指向调用函数的对象;静态成员函数是没有this指针的,因为静态成员函数只能访问静态成员变量,而一个类的所有成员对象共享静态成员变量,不需要用this指针标识要处理的数据

float Point3d::getA(){return sqrt(_x*_x+_y*_y+_z*_z);}float getA(const Point3d *this){return sqrt(this->_x* this->_x + this->_y * this->_y + this->_z * this->_z);}

1. 概述

简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表其中存放着该类所有的虚函数对应的函数指针。例:

其中:

  • B的虚函数表中存放着B::foo和B::bar两个函数指针。
  • D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写覆盖(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz。

提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响。

2. 虚函数表构造过程

从编译器的角度来说,B的虚函数表很好构造,D的虚函数表构造过程相对复杂。下面给出了构造D的虚函数表的一种方式(仅供参考):

提示:该过程是由编译器完成的,因此也可以说:虚函数替换过程发生在编译时。

3. 虚函数调用过程

以下面的程序为例:

编译器只知道pb是B*类型的指针,并不知道它指向的具体对象类型 :pb可能指向的是B的对象,也可能指向的是D的对象。

但对于“pb->bar()”,编译时能够确定的是:此处operator->的另一个参数是B::bar(因为pb是B*类型的,编译器认为bar是B::bar),而B::bar和D::bar在各自虚函数表中的偏移位置是相等的。

无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数中的偏移值,待运行时,能够确定具体类型,并能找到相应vptr了,就能找出真正应该调用的函数。

提示:本人曾在“C/C++杂记:深入理解数据成员指针、函数成员指针”一文中提到:虚函数指针中的ptr部分为虚函数表中的偏移值(以字节为单位)加1。

B::bar是一个虚函数指针, 它的ptr部分内容为9,它在B的虚函数表中的偏移值为8(8+1=9)。

当程序执行到“pb->bar()”时,已经能够判断pb指向的具体类型了:

  • 如果pb指向B的对象,可以获取到B对象的vptr,加上偏移值8((char*)vptr + 8),可以找到B::bar。
  • 如果pb指向D的对象,可以获取到D对象的vptr,加上偏移值8((char*)vptr + 8) ,可以找到D::bar。
  • 如果pb指向其它类型对象...同理...

4. 多重继承

当一个类继承多个类,且多个基类都有虚函数时,子类对象中将包含多个虚函数表的指针(即多个vptr),例:

其中:D自身的虚函数与B基类共用了同一个虚函数表,因此也称B为D的主基类(primary base class)。

虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。

虚函数的调用过程,与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,以如下面的程序为例:

原创粉丝点击