虚函数多态的实现细节

之前老是被问到虚函数多态的事情.......有个模棱两可的印象,正好遇到这个帖子了,所以再学习学习

http://www.cnblogs.com/shouce/p/5453729.html

1、什么是虚函数

简单地说:那些被virtual关键字修饰的成员函数就是虚函数。其主要作用就是实现多态性

多态性是面向对象的核心:它的主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现。通常多态性被简单的描述为“一个接口,多个实现”。在C++里面具体的表现为通过基类指针访问派生类的函数和方法

2、联编

在详细解释虚函数多态是怎么实现之前,我们先了解下联编的概念——就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址。按照联编所进行的阶段不同,可分为静态和动态两种。

静态联编:在编译阶段就将函数实现和函数调用关联起来称之为静态联编,静态联编在编译阶段就必须了解所有的函数或模块执行所需要的检测信息,它对函数的选择是基于指向对象的指针(或者引用)的类型

动态联编:在程序执行的时候才进行这种关联称之为动态联编,动态联编对成员函数的选择是基于对象类型而不是指针或者引用,不同的对象类型将做出不同的编译结果。C语言中,所有的联编都是静态联编。C++中一般情况下联编也是静态联编,但是一旦涉及到多态性和虚函数就必须使用动态联编

3、揭秘动态联编

编译器到底做了什么实现虚函数的动态联编呢?事实上编译器对每个包含虚函数的类创建了一个表(vtable),我们称之为虚表。在vtable中,编译器放置特定类的虚函数地址,在每个带有虚函数的类中,编译器秘密地置一指针,称为vpointer(常缩写为vptr),指向这个对象的vtable。通过基类指针调用虚函数时(即多态调用),编译器静态地取得这个vptr,并在vtable表中查找函数地址的代码,这样就能调用正确的函数使动态联编发生。为每个类设置vtable、初始化vptr、为虚函数调用插入代码,所有这些都是自动发生的,多以我们不必担心这些。利用虚函数,这个对象合适的函数就能被调用,哪怕在编译器还不知道这个对象的特定类型的情况下。(《Thinking in C++》)

在任何类中不存在显示的类型信息,可对象中必须存放类信息,否则类型不可能在运行时建立。那这个类信息时什么呢?我们来看下面几个类:

 1 class A
 2 {
 3  public:
 4      void fun1() const{}
 5      int fun2() const{return a;}
 6  private:
 7      int a;
 8 };
 9
10 class B
11 {
12  public:
13      virtual void fun1() const{}
14      int fun2() const{return a;}
15  private:
16      int a;
17 };
18
19 class C
20 {
21  public:
22      virtual void fun1() const{}
23      virtual int fun2() const {return a;}
24  private:
25      int a;
26 };

以上三个类中:

A类没有虚函数,sizeof(A) = 4,类A的长度就是其成员变量整型a的长度;

B类有一个虚函数,sizeof(B) = 8;

C类有两个虚函数,sizeof(C) = 8;有一个虚函数和有两个虚函数的类的长度没有区别,其实它们的长度就是A的长度加一个void指针的长度,它反映出,如果有一个或多个虚函数,编译器在这个结构中插入一个指针(vptr)。在B和C之间没有区别。这是因为vptr指向一个存放地址的表,只需要一个指针,因为所有虚函数地址都包含在这个表中。

这个vptr就可以看作类的类型信息。

那我们来看看编译器是怎么建立vptr指向的这个虚函数表的。先看下面两个类:

 1 class Base
 2 {
 3  pubic:
 4      void bfun(){}
 5      virtual void vfun1(){}
 6      virtual int vfun2(){}
 7  private:
 8      int a;
 9 };
10
11 class Derived : public Base
12 {
13  public:
14      void dfun(){}
15      virtual void vfun1(){}
16      virtual int vfun3(){}
17  private:
18      int b;
19 };

两个类vptr指向的虚函数表(vtable)分别如下:

Base类                                                    Derived类

vptr——>|  &Base::vfun1  |                       vptr——>|  &Derived::vfun1  |

|  &Base::vfun2  |                              |  &Base::vfun2     |

|  &Derived::vfun3     |

每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个vtable(如上所示),在这个表中,编译器放置了在这个类中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义,编译器就使用基类的这个虚函数地址(在Derived的vtable中,vfun2的入口就是这种情况。)然后编译器在这个类中放置vptr。当使用简单继承时,对于每个对象只有一个vptr。vptr必须被初始化为指向相应的vtable,这在构造函数中发生。

一旦vptr被初始化为指向相应的vtable,对象就“知道”它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。

VPTR常常位于对象的开头,编译器能很容易地取到VPTR的值,从而确定VTABLE的位置。VPTR总指向VTABLE的开始地址,所有基类和它的子类的虚函数地址(子类自己定义的虚函数除外)在VTABLE中存储的位置总是相同的,如上面Base类和Derived类的vtable中 vfun1和vfun2的地址总是按相同的顺序存储。编译器知道vfun1位于vptr处,vfun2位于vptr+1处,因此在用基类指针调用虚函数时,编译器首先获取指针指向对象的类型信息(vptr),然后就去调用虚函数。如一个Base类指针(pBase)指向了一个Derived对象,那 pBase->vfun2()被编译器翻译为 vptr+1 的调用,因为虚函数vfun2的地址在vtable中位于索引为1的位置上。同理,pBase->vfun3()被编译器翻译为vptr+2的调用。这就是所谓的晚绑定。

时间: 2024-07-28 14:38:28

虚函数多态的实现细节的相关文章

揭秘虚函数多态的实现细节

1.什么是虚函数 简单地说:那些被virtual关键字修饰的成员函数就是虚函数.其主要作用就是实现多态性. 多态性是面向对象的核心:它的主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现.通常多态性被简单的描述为“一个接口,多个实现”.在C++里面具体的表现为通过基类指针访问派生类的函数和方法.看下面这段简单的代码: 1 class A 2 { 3 public: 4 void print(){cout << "this is A" <&

4.虚函数-多态

1.多态 多态的条件: (1):继承 (2):父类中有虚函数 (3):在子类中重新实现父类的虚函数(覆盖虚表) (4):把子类对象/指针赋值给父类的引用/指针 (5):通过父类的引用/指针来调用虚函数(只能调用父类中存在的函数) 用C++类以及多态来封装pthread进程 class CppThread{ public: CppThread(){} ~CppThread(){} void start(); virtual void run(){} protected: pthread_t id;

【继承与多态】C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态

实现基类(父类)以及派生类(子类),验证继承与转换--赋值兼容规则: 子类对象可以赋值给父类对象(切割/切片) 父类对象不能赋值给子类对象 父类的指针/引用可以指向子类对象 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成) #include<iostream> using namespace std; class People    //父类或者基类 { public:     void Display()     {         cout << "_na

C++:抽象基类和纯虚函数的理解

转载地址:http://blog.csdn.net/acs713/article/details/7352440 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层. ⑴抽象类的定义: 称带有纯虚函数的类为抽象类. ⑵抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作.所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些

虚函数与多态小览

一.文章来由 Bill又写文章来由了哇~~早就想好好搞清这个问题了,这是c++领域里面比较难搞定的一块知识点,而且最近在看设计模式,里面有涉及这块,之前学过的不用容易玩忘记,于是就干脆研究透一点,也好碰到.用到的时候不心慌~于是有了这篇文章. 二.从编译时和运行时说起 2.1 编译时: 顾名思义就是正在编译的时候.就是编译器帮你把源代码翻译成机器能识别的代码.(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.比如Java只有JVM识别的字节码,C#中只有CLR能识别的MSIL)

C++中类的多态与虚函数的使用

C++的三大特性:封装.继承.多态.以前学的时候自己没去总结,记得在一本c++入门的书讲得还是比较清楚.今天上网找了一下多态,找到下面这篇文章写得比较清晰. http://pcedu.pconline.com.cn/empolder/gj/c/0503/574706.html 类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visual BASIC 6.0 是典型

多态&amp;虚函数

(1).对象类型: a.静态类型:对象声明时的类型,编译的时候确定 b.动态类型:对象的类型是运行时才能确定的 class A {}; class B:public A {}; int main() { B* b; A* a=b;//a的静态类型是A*,动态类型(运行时)类型是B* return 0; } (2).多态 a.静态多态:函数重载.泛性编程 int Add(int a,int b) { return a+b; } float Add(float a,float b) { return

c++虚函数与多态实例分析

1 #include <iostream> 2 #include <complex> 3 using namespace std; 4 5 class Base 6 { 7 public: 8 Base() {cout<<"Base-ctor"<<endl;} 9 ~Base() {cout<<"Base-dtor"<<endl;} 10 virtual void f(int){cout<

虚函数与多态

如果我们有三个类Person.Teacher.Student它们之间的关系例如以下: 类的关系图 普通成员函数 [Demo1] 依据这个类图,我们有以下的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__ #include <string> #include <iostream> class Person { public: Person(const string& name, int age) : m_name(name), m_