OS:Windows 7

关键字:VS2015,C++,V-Table,虚表,虚函数。

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。下面举例解析虚表。

单一继承情况:

#include "stdafx.h"
#include <string>
#include <iostream> using namespace std; class Base
{
public:
virtual std::string Name();
}; std::string Base::Name()
{
return "Base";
} class A : public Base
{
public:
std::string Name() override;
virtual std::string AName();
}; std::string A::Name()
{
return AName();
} std::string A::AName()
{
return "A";
} int main()
{
cout << "Size of Base: " << sizeof(Base) << endl;
cout << "Size of A: " << sizeof(A) << endl; Base* pBase = new Base();
cout << pBase->Name() << endl;
A* pA = new A();
cout << pA->Name() << endl;
Base* pBaseA = pA;
cout << pBaseA->Name() << endl; return ;
}

控制台输出:

x86:

Size of Base: 4
Size of A: 4
Base
A
A

x64:

Size of Base:
Size of A:
Base
A
A

虚表结构:

总结:

  • Base和A对象的大小都是一个虚表指针的大小。一个指针大小在x86 (32bit)程序里是4个字节,在x64(64bit)程序里面是8个字节。
  • Watch窗口没有显示虚表里面的AName,在Watch立面添加“(*((Base*)pA)).__vfptr[1]”可以看到。

多重继承情况:

#include "stdafx.h"
#include <string>
#include <iostream> using namespace std; class A
{
public:
virtual string AName() { return "A"; }
}; class B
{
public:
virtual string BName() { return "B"; }
}; class C : public A, public B
{
public:
virtual string BName() { return "C"; }
}; int main(int argc, _TCHAR* argv[])
{
cout << "Size of A: " << sizeof(A) << endl;
cout << "Size of B: " << sizeof(B) << endl;
cout << "Size of C: " << sizeof(C) << endl;
C* pC = new C();
C* pC1 = new C();
cout << pC->BName() << endl;
B* pB = static_cast<B*>(pC);
cout << pB->BName() << endl;
//A* pA = static_cast<A*>(pB);
A* pA = dynamic_cast<A*>(pB);
cout << pA->AName() << endl;
A* pA1 = reinterpret_cast<A*>(pB);
cout << pA1->AName() << endl;
return ;
}

控制台输出:

x86:

Size of A:
Size of B:
Size of C:
C
C
A
C

x64:

Size of A:
Size of B:
Size of C:
C
C
A
C

看到这个结果有没有让你奇怪的地方?为什么“pA1->AName()”输出是C?

虚表结构:

总结:

  • pC和pC1是类C的两个实例指针,这两个实例的虚表是一样的,也就是说虚表是属于类的,一个类有一个虚表。
  • 因为类C重写了类B的BName函数,所以C的虚表里存的是C::BName
  • “B* pB = static_cast<B*>(pC);”,父类的指针来操作一个子类,虚函数实现了多态。
  • “A* pA = static_cast<A*>(pB);”是编译不通过的,因为A和B是不相关的两个类,也就是没有继承关系。
  • “A* pA = dynamic_cast<A*>(pB);”是可以的,因为dynamic_cast会在运行时进行类型检查,dynamic_cast是最安全的,但是效率最低。
  • “A* pA1 = reinterpret_cast<A*>(pB);”是可以编译通过了,但是运行时会有意想不到的结果。reinterpret_cast进行了强制类型转换,但是不会纠正虚表。
  • 派生类C有两个虚表指针分别指向两个虚表。

虚拟继承情况:

#include "stdafx.h"
#include <string>
#include <iostream> using namespace std; class Base
{
public:
virtual string Name();
}; string Base::Name()
{
return "Base";
} class A : virtual public Base
{
public:
string Name() override;
virtual string AName();
}; string A::Name()
{
return AName();
return "A";
} string A::AName()
{
return "A";
} int main()
{
cout << "Size of Base: " << sizeof(Base) << endl;
cout << "Size of A: " << sizeof(A) << endl; Base* pBase = new Base();
cout << pBase->Name() << endl;
A* pA = new A();
cout << pA->Name() << endl;
Base* pBaseA = pA;
cout << pBaseA->Name() << endl; return ;
}

控制台输出:

x86:

Size of Base:
Size of A:
Base
A
A

x64:

Size of Base:
Size of A:
Base
A
A

虚表结构:

总结:

  • 以x64为例,相比非虚单继承,A的大小打了16个字节,是因为A多了一个虚表指针指向用于存放A的虚函数AName的虚表,另外虚继承本身占了8个字节。

实例解析C++虚表的更多相关文章

  1. exec函数族实例解析

    exec函数族实例解析 fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间.堆.栈等资源的副本.注意,子进程持有的是上述存储空间的 ...

  2. [Reprint] C++函数模板与类模板实例解析

    这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下   本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...

  3. [Reprint]C++普通函数指针与成员函数指针实例解析

    这篇文章主要介绍了C++普通函数指针与成员函数指针,很重要的知识点,需要的朋友可以参考下   C++的函数指针(function pointer)是通过指向函数的指针间接调用函数.相信很多人对指向一般 ...

  4. JavaWeb实现文件上传下载功能实例解析

    转:http://www.cnblogs.com/xdp-gacl/p/4200090.html JavaWeb实现文件上传下载功能实例解析 在Web应用系统开发中,文件上传和下载功能是非常常用的功能 ...

  5. Android实例-Delphi开发蓝牙官方实例解析(XE10+小米2+小米5)

    相关资料:1.http://blog.csdn.net/laorenshen/article/details/411498032.http://www.cnblogs.com/findumars/p/ ...

  6. Android开发之IPC进程间通信-AIDL介绍及实例解析

    一.IPC进程间通信 IPC是进程间通信方法的统称,Linux IPC包括以下方法,Android的进程间通信主要采用是哪些方法呢? 1. 管道(Pipe)及有名管道(named pipe):管道可用 ...

  7. easyUI:ComboTree and comselector使用实例解析

    ComboTree 使用场景:故名思意,ComboTree是combox和Tree的结合体,在需要通过选择得到某一个node值的时候触发. 栗子: 定义: 使用标签创建树形下拉框. Comselect ...

  8. Maven--多模块依赖实例解析(五)

    <Maven--搭建开发环境(一)> <Maven--构建企业级仓库(二)> <Maven—几个需要补充的问题(三)> <Maven—生命周期和插件(四)&g ...

  9. SoapUI简介和入门实例解析

    SoapUI简介 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到Ecl ...

随机推荐

  1. VM11安装Mac OS X 10.10

    工具/原料 1. VMware Workstation 11.12 2. unlocker 206(for OS X 插件补丁) 3. Mac OS X 10.10镜像 方法/步骤   有图有真相,哈 ...

  2. Linux SPI框架(下)

    分类: Linux驱动程序2012-07-11 20:44 3006人阅读 评论(2) 收藏 举报 linuxstructlistclassdelayprocessing 水平有限,描述不当之处还请之 ...

  3. 在iOS上自动检测内存泄露

    手机设备的内存是一个共享资源.应用程序可能会不当的耗尽内存.崩溃,或者遭遇大幅度的性能降低. Facebook iOS客户端有很多功能,并且它们共享同一块内存空间.如果任何特定的功能消耗过多的内存,就 ...

  4. Debug 之 VS2010网站生成成功,但是发布失败

    用vs做好了网站.清理解决方案和重新生成解决方案都可以.但是发布不能成功.发布不能成功,有错误还好,郁闷的是竟然没有错误提示. 解决方法: 1.发布文件夹权限问题.重新找个地方建立一个发布文件夹即可. ...

  5. 对JavaScript对象数组按指定属性和排序方向进行排序

    引子 在以数据为中心的信息系统中,以表格形式展示数据是在常见不过的方式了.对数据进行排序是必不可少的功能.排序可以分为按单个字段排序和按多个字段不同排序方向排序.单字段排序局限性较大,不能满足用户对数 ...

  6. javah 生成header file 报错 问题解决

    环境:Android Studio, Mac OS 目标: 用javah 为MainActivity.class 生成 jni header 文件 正确的命令是 cd <class文件的路径&g ...

  7. 使用angular的ng-repeat遇到的一个问题

    问题描述:ng-repeat绑定的数据集动态更新之后其包裹的子元素所绑定的事件全部丢失:问题详述:问题代码如下: <style> img{width:100px;height:100px; ...

  8. 【暴力模拟】UVA 1594 - Ducci Sequence

    想麻烦了.这题真的那么水啊..直接暴力模拟,1000次(看了网上的200次就能A)后判断是否全为0,否则就是LOOP: #include <iostream> #include <s ...

  9. PHP之文件目录基础操作

    我们知道,临时声明的变量是保存在内存中的,即便是静态变量,在脚本运行完毕后也会被释放掉,so,想长久保存一个变量的内容,方法之一就是写到文件中,放到硬盘或服务器上,为此文件操作就必须很熟悉. 1.文件 ...

  10. 忘记root口令的处理方法

    最近在拜读<Unix & Linux 大学教程>这本书,这本书写得不错.关于Unix和Linux比较介绍比较深入根源,内容也比较详细,很值得去拜读.       在书中的附录中有一 ...