C++虚函数表剖析
关键词:虚函数。虚表,虚表指针,动态绑定,多态
一、概述
为了实现C++的多态,C++使用了一种动态绑定的技术。
这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是怎样实现动态绑定的。
二、类的虚表
每一个包括了虚函数的类都包括一个虚表。
我们知道,当一个类(A)继承还有一个类(B)时。类A会继承类B的函数的调用权。所以假设一个基类包括了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包括虚函数的基类。那么这个类也拥有自己的虚表。
我们来看下面的代码。
类A包括虚函数vfunc1。vfunc2。因为类A包括虚函数,故类A拥有一个虚表。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
类A的虚表如图1所看到的。
图1:类A的虚表示意图
虚表是一个指针数组,其元素是虚函数的指针,每一个元素相应一个虚函数的函数指针。须要指出的是。普通的函数即非虚函数。其调用并不须要经过虚表,所以虚表的元素并不包括普通函数的函数指针。
虚表内的条目。即虚函数指针的赋值发生在编译器的编译阶段。也就是说在代码的编译阶段。虚表就能够构造出来了。
三、虚表指针
虚表是属于类的,而不是属于某个详细的对象,一个类仅仅须要一个虚表就可以。同一个类的全部对象都使用同一个虚表。
为了指定对象的虚表。对象内部包括一个虚表的指针,来指向自己所使用的虚表。为了让每一个包括虚表的类的对象都拥有一个虚表指针,编译器在类中加入了一个指针,*__vptr。用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自己主动被设置为指向类的虚表。
图2:对象与它的虚表
上面指出,一个继承类的基类假设包括虚函数,那个这个继承类也有拥有自己的虚表,故这个继承类的对象也包括一个虚表指针,用来指向它的虚表。
四、动态绑定
讲到这里,大家一定会好奇C++是怎样利用虚表和虚表指针来实现动态绑定的。我们先看下面的代码。
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1();
void func1();
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2();
void func2();
private:
int m_data1, m_data4;
};
类A是基类,类B继承类A。类C又继承类B。类A,类B,类C,其对象模型例如以下图3所看到的。
图3:类A,类B,类C的对象模型
因为这三个类都有虚函数,故编译器为每一个类都创建了一个虚表,即类A的虚表(A vtbl)。类B的虚表(B vtbl),类C的虚表(C vtbl)。类A,类B,类C的对象都拥有一个虚表指针,*__vptr,用来指向自己所属类的虚表。
类A包括两个虚函数,故A vtbl包括两个指针,分别指向A::vfunc1()和A::vfunc2()。
类B继承于类A。故类B能够调用类A的函数。但因为类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。
类C继承于类B,故类C能够调用类B的函数,但因为类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的近期的一个类的函数)和C::vfunc2()。
尽管图3看起来有点复杂,可是仅仅要抓住“对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的近期的一个类的虚函数”这个特点,便能够高速将这几个类的对象模型在自己的脑海中描绘出来。
非虚函数的调用不用经过虚表。故不须要虚表中的指针指向这些函数。
假设我们定义一个类B的对象。因为bObject是类B的一个对象,故bObject包括一个虚表指针,指向类B的虚表。
int main()
{
B bObject;
}
如今,我们声明一个类A的指针p来指向对象bObject。
尽管p是基类的指针仅仅能指向基类的部分,可是虚表指针亦属于基类部分,所以p能够訪问到对象bObject的虚表指针。bObject的虚表指针指向类B的虚表,所以p能够訪问到B vtbl。
如图3所看到的。
int main()
{
B bObject;
A *p = & bObject;
}
当我们使用p来调用vfunc1()函数时,会发生什么现象?
int main()
{
B bObject;
A *p = & bObject;
p->vfunc1();
}
程序在执行p->vfunc1()时,会发现p是个指针。且调用的函数是虚函数,接下来便会进行下面的步骤。
首先,依据虚表指针p->__vptr来訪问对象bObject相应的虚表。尽管指针p是基类A*类型。可是*__vptr也是基类的一部分,所以能够通过p->__vptr能够訪问到对象相应的虚表。
然后,在虚表中查找所调用的函数相应的条目。因为虚表在编译阶段就能够构造出来了,所以能够依据所调用的函数定位到虚表中的相应条目。
对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1相应的条目。
最后,依据虚表中找到的函数指针。调用函数。从图3能够看到。B vtbl的第一项指向B::vfunc1()。所以 p->vfunc1()实质会调用B::vfunc1()函数。
假设p指向类A的对象,情况又是怎么样?
int main()
{
A aObject;
A *p = &aObject;
p->vfunc1();
}
当aObject在创建时,它的虚表指针__vptr已设置为指向A vtbl,这样p->__vptr就指向A vtbl。vfunc1在A vtbl相应在条目指向了A::vfunc1()函数,所以 p->vfunc1()实质会调用A::vfunc1()函数。
能够把以上三个调用函数的步骤用下面表达式来表示:
(*(p->__vptr)[n])(p)
能够看到。通过使用这些虚函数表。即使使用的是基类的指针来调用函数。也能够达到正确调用执行中实际对象的虚函数。
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为执行时多态。动态绑定差别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就能够确定下来了。
那么,什么时候会执行函数的动态绑定?这须要符合下面三个条件。
- 通过指针来调用函数
- 指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast。能够參考本文的參考资料)
- 调用的是虚函数
假设一个函数调用符合以上三个条件,编译器就会把该函数调用编译成动态绑定,其函数的调用过程走的是上述通过虚表的机制。
五、总结
封装。继承,多态是面向对象设计的三个特征,而多态能够说是面向对象设计的关键。C++通过虚函数表,实现了虚函数与对象的动态绑定。从而构建了C++面向对象程序设计的基石。
參考资料
- 《C++ Primer》第三版,中文版,潘爱民等译
- http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/
- 侯捷《C++最佳编程实践》视频,极客班,2015
- Upcasting and Downcasting, http://www.bogotobogo.com/cplusplus/upcasting_downcasting.php
附录
C++虚函数表剖析的更多相关文章
- 【C++ Primer | 15】C++虚函数表剖析①
概述 为了实现C++的多态,C++使用了一种动态绑定的技术.这个技术的核心是虚函数表(下文简称虚表).本文介绍虚函数表是如何实现动态绑定的. C++多态实现的原理: • 当类中声明虚函数时,编译器会 ...
- 【C++ Primer | 15】C++虚函数表剖析②
多重继承 下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系. 注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记 ...
- 深入剖析C++多态、VPTR指针、虚函数表
在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...
- C++ 虚函数表解析
转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...
- C++ 虚函数表解析(转载)
转载自:陈皓 http://blog.csdn.net/haoel/article/details/1948051/ 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型 ...
- 转载:C++ 虚函数表解析
目录(?)[+] 转载:http://blog.csdn.net/haoel/article/details/1948051# 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而 ...
- C++虚函数表解析(转)
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
- C++ 虚函数表解析(比较清楚,还可打印虚函数地址)
C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父 ...
- C++ 虚函数表决心
C++ 虚函数表解析 xml:namespace prefix = o /> 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制. 关 ...
随机推荐
- Jmeter之https请求
Jmeter之录制https脚本,网上介绍了好多种方法,大家自行百度. 如果手写https脚本,该如何做呢? 方法:http信息头管理器,加入User-Agent参数 案例:手写百度的搜索:哈哈 请 ...
- close - 关闭一个文件描述符
SYNOPSIS 总览 #include <unistd.h> int close(int fd); DESCRIPTION 描述 close 关闭 一个 文件 描述符 , 使它 不在 指 ...
- Perl字符集[\d\D]表示任何字符(所有数字和非数字,包括换行符),“.”表示除了换行符以外的所有字符。
Perl字符集[\d\D]表示任何字符(所有数字和非数字,包括换行符),“.”表示除了换行符以外的所有字符.
- PHP 下基于 php-amqp 扩展的 RabbitMQ 简单用例 (一) -- 安装 AMQP 扩展和 Direct Exchange 模式
Windows 安装 amqp 扩展 RabbitMQ 是基于 amqp(高级消息队列协议) 协议的.使用 RabbitMQ 前必须为 PHP 安装相应的 amqp 扩展. 下载相应版本的 amqp ...
- 【C语言】控制台窗口图形界面编程(一)句柄和文本属性
目录 00. 目录 01. 句柄 02. GetStdHandle函数 03. CloseHandle函数 04. SetConsoleTextAttribute函数 05. 十进制颜色对照表 06. ...
- NET使用SuperSocket完成TCP/IP通信
1)为什么使用SuperSocket? 性能高,易上手.有中文文档,我们可以有更多的时间用在业务逻辑上,SuperSocket有效的利用自己的协议解决粘包 2)SuperSocket的协议内容? 命令 ...
- IIS更改根目录
服务器中打开IIS管理器,选择网站,再选展开后的站点,点击右边高级设置,最后更改弹框中的物理地址即可:
- vue多视图
第一步 在app.vue中 <router-view class="b" name="header"> </router-view> ...
- js 循环 js创建数组
循环 for (var i = 0; i < myArray.length; i++) { console.log(myArray[i]); }; for (var arr in myArray ...
- Dijkstra算法C++实现总结
问题描述 求无负权图中点s到点t的最短凝聚力 备注 标准说法中,"缩短"/"松弛"(relax)操作是对边进行的.下面为了行文方便,将其拓展到点.即以下操作,其 ...