目录

1.虚函数列表的位置

2.虚函数列表的内容

3.链式继承中虚函数列表的内容

 

注:

虚函数列表 又称为虚表, vtbl , 指向它的指针称为vptr, vs2019中称为__vfptr

操作系统: windows 10 专业版 64位

编译器: Visual Studio 2019 Community

 

1.虚函数列表的位置

结论

编译器一般会保证指向虚函数列表的指针存在于对象实例中最前面的位置

而虚函数列表中的内容, 就是多个函数指针

代码验证:

首先声明一个基类Base和一个派生类Derived

class  Base
{
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
}; class Derived : public Base
{
virtual void g() override { std::cout << "Derived::g" << std::endl; }
virtual void h1() { std::cout << "Derived::i1" << std::endl; }
};

然后实例化一个派生类的对象

Derived derived;

现在我们打印出该对象的地址

std::cout << "derived对象的地址: " << (&derived) << std::endl;

由于我们假定指向虚函数列表的<指针>存在于对象实例中最前面的位置

那么我们可以认定, derived对象的地址中的开头是一个指针的地址(称之为指针pA)

而这个指针(pA)指向虚函数列表中的开头, 也就是一个函数指针(称之为指针pF)

所以这个指针(pA), 是一个指向指针的指针, 即指向指针(pF)的指针(pA)

基于这个推测, 我们将derived对象的地址指针pA的地址进行一个类型转换

使用reinterpret_cast<int**>关键字, 将其转换为一个指向指针的指针

reinterpret_cast<int**>(&derived)

现在我们对这个指针(pA)的地址, 取其内容, 就会得到pA的内容

std::cout << "derived对象中第一个指针的内容: " << *reinterpret_cast<int**>(&derived) << std::endl;

根据上面的推测, 这个内容, 就是虚函数列表的地址

控制台输出如下:

通过vs2019中, 可以直接查看到derived的__vfptr对象的地址, 和控制台打印的内容是相同的

 

2.单继承中虚函数列表的内容

基类中有4个函数, 分别为

f();
g();
h();
i();

派生类中有2个函数,分别为

g();
i1();

现在使用表格的方式表示出来, 方便查看, 进行了override的函数, 会放在同一行

结论 在虚函数列表中, 函数的布局如下图所示:

代码验证请看链式继承中虚函数列表的内容

 

3.链式继承中虚函数列表的内容

声明3个类, 其继承关系为Derived继承Base2, Base2继承Base1

class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
}; class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
}; class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
};

用表格的方法表示为:

!()(https://silenzio-markdown-image-hosting-service.oss-cn-beijing.aliyuncs.com/博客图床/C%2B%2B 链��%继承下的虚函数列表/1a885406e3318ccbbf5dce9961e1599.png)

结论

在虚函数列表中, 函数的布局如下图所示:

Derive只有一个虚函数表, 是在Base2的虚函数表上, 进行类似于单继承的覆盖
同理, Base2也有一张虚函数表, 是在Base1的虚函数表上, 进行单继承的覆盖

代码验证

////////////////////////////////////////////////////////////////////////////////
// 链式继承
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
}; class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
}; class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
}; using Fun = void(*)(void); int main()
{
Fun pFun = nullptr; // 操作系统: windows 10 专业版 32/64位都可以
// 编译器 : Visual Studio 2019 Community
std::cout << sizeof(int) << std::endl; // 32位:4 64位:4
std::cout << sizeof(long) << std::endl; // 32位:4 64位:4
std::cout << sizeof(int*) << std::endl; // 32位:4 64位:8 std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base1的虚表如下 " << std::endl;
Base1 base1; std::cout << "base1对象的地址: " << (&base1) << std::endl;
std::cout << "base1对象中第一个指针的地址(不是内容): " << (&base1) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "base1对象中第一个指针的内容: " << *reinterpret_cast<int**>(&base1) << std::endl;
std::cout << "base1虚函数表地址: " << *reinterpret_cast<int**>(&base1) << std::endl; // 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "base1虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))))) << std::endl;
pFun = reinterpret_cast<Fun>(* (reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)))));
std::cout << "base1虚函数表 — 第一个函数内容:";
pFun(); // base1::f
std::cout << std::endl; // 注意次数的偏移量, 32位偏移量是+1, 64位偏移量是+2
std::cout << "base1虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))+1*(sizeof(int*)/sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第二个函数内容:";
pFun(); // base1::g
std::cout << std::endl; std::cout << "base1虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl; std::cout << "base1虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl; std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base2的虚表如下 " << std::endl;
Base2 base2; std::cout << "base2对象的地址: " << (&base2) << std::endl;
std::cout << "base2对象中第一个指针的地址(不是内容): " << (&base2) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "base2对象中第一个指针的内容: " << *reinterpret_cast<int**>(&base2) << std::endl;
std::cout << "base2虚函数表地址: " << *reinterpret_cast<int**>(&base2) << std::endl; // 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "base2虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)))));
std::cout << "base2虚函数表 — 第一个函数内容:";
pFun(); // base2::f
std::cout << std::endl; std::cout << "base2虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第二个函数内容:";
pFun(); // base1::g
std::cout << std::endl; std::cout << "base2虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl; std::cout << "base2虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl; std::cout << "base2虚函数表 — 第五个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虚函数表 — 第五个函数内容:";
pFun(); // base2::h1
std::cout << std::endl; std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Derived的虚表如下 " << std::endl;
Derived Derived; std::cout << "Derived对象的地址: " << (&Derived) << std::endl;
std::cout << "Derived对象中第一个指针的地址(不是内容): " << (&Derived) << std::endl;
// &base1 是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个虚表(虚表的内容也是一堆指针)
std::cout << "Derived对象中第一个指针的内容: " << *reinterpret_cast<int**>(&Derived) << std::endl;
std::cout << "Derived虚函数表地址: " << *reinterpret_cast<int**>(&Derived) << std::endl; // 虚函数表地址, 也是一个指向指针的指针
// 对它进行*操作, 得到了这个指针的内容, 即它指向的位置, 是一个函数指针
std::cout << "Derived虚函数表 — 第一个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)))));
std::cout << "Derived虚函数表 — 第一个函数内容:";
pFun(); // base2::f
std::cout << std::endl; std::cout << "Derived虚函数表 — 第二个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第二个函数内容:";
pFun(); // Derived::g
std::cout << std::endl; std::cout << "Derived虚函数表 — 第三个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第三个函数内容:";
pFun(); // base1::h
std::cout << std::endl; std::cout << "Derived虚函数表 — 第四个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第四个函数内容:";
pFun(); // base1::i
std::cout << std::endl; std::cout << "Derived虚函数表 — 第五个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第五个函数内容:";
pFun(); // base2::h1
std::cout << std::endl; std::cout << "Derived虚函数表 — 第六个函数地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虚函数表 — 第六个函数内容:";
pFun(); // Derived::i1
std::cout << std::endl; return 0;
}

C++ 链式继承下的虚函数列表的更多相关文章

  1. C++链式继承

            继承,对于学习C++的每一个人来说,都不会陌生.在Qt的开发中,如果你需要对一个无边框的界面支持move操作,那么你就得通过继承重写虚函数来实现,这并不难,但如果我还需要对一个按钮支持 ...

  2. 虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

    #include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout < ...

  3. 谈谈c++中继承中的虚函数

      c++继承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问 ...

  4. ThinkPHP通过类的链式继承优化空操作的实现

    上篇<ThinkPHP空操作和空控制器的处理>中,在处理空操作时修改了父类Controller.class.php中代码,不到万不得已不能 修改基类控制器中的原码,此时可在子类与父类之间, ...

  5. C++学习 之 类的继承中的虚函数(笔记)

    1.多态行为 多态是面向对象语言的一种特征,让我们能够以类似的方式处理不同类型的对象.在C++中我们可以通过继承层次结构实现子类型多态. 我们可以通过下面的代码进一步了解多态: #include< ...

  6. C++ 子类继承父类纯虚函数、虚函数和普通函数的区别

    C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...

  7. C++继承-重载-多态-虚函数

    C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...

  8. DLL中类的显式链接(用虚函数进行显式链接)

    DLL的显式链接在某些时候比隐式链接具有更大的灵活性.比如,如果在运行时发现DLL无法找到,程序可以显示一个错误信息并能继续运行.当你想为你的程序提供插件服务时,显式链接也很有用处. 显式链接到全局C ...

  9. 【整理】C++虚函数及其继承、虚继承类大小

    参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/deta ...

随机推荐

  1. Python--day37--多进程中的方法join()

    1,多进程中的方法join()的作用: 感知一个子进程的结束,将异步的程序改为同步 #join() import time from multiprocessing import Process de ...

  2. Echarts构建图表

    Echarts学习-构建图表 相信有很多的前端开发人员在开发Echarts图表的过程中都遇到对图表结构过无从下手,面对一大堆的专业词汇一脸懵逼的样子,在经过了一段时间的踩坑后,终于摸索出了一套完善的学 ...

  3. Linux 内核驱动结构嵌入

    如同大部分驱动核心结构的情形, device_driver 结构常常被发现嵌到一个更高级的, 总 线特定的结构. lddbus 子系统不会和这样的趋势相反, 因此它已定义了它自己的 ldd_drive ...

  4. hdu 6851 Vacation(思维+贪心)

    传送门 •题意 有编号0到n,n+1辆车排队过红绿灯,从0到n离交通灯线越来越近 每辆车都有一个最大速度v,车身长度l,和离交通灯线的距离s, 一辆车头到达线则说明这辆车已到达线 如果一辆车前面没有紧 ...

  5. 关于instanface的问题

    nstanceof关键字来判断某个对象是否属于某种数据类型.报错  代码如下 package cn.lijun.demo3; import cn.lijun.demo.Person;import cn ...

  6. [梁山好汉说IT] 熵的概念 & 决策树ID3如何选择子树

    [梁山好汉说IT] 熵的概念 & 决策树ID3如何选择子树 0x00 摘要 记录对概念的理解,用梁山好汉做例子来检验是否理解正确. 0x01 IT概念 1. 事物的信息和信息熵 1.1 事物的 ...

  7. 【重学Node.js 第1&2篇】本地搭建Node环境并起RESTful Api服务

    本地搭建Node环境并起RESTful Api服务 课程介绍看这里:https://www.cnblogs.com/zhangran/p/11963616.html 项目github地址:https: ...

  8. Mysql的SQL优化指北

    概述 在一次和技术大佬的聊天中被问到,平时我是怎么做Mysql的优化的?在这个问题上我只回答出了几点,感觉回答的不够完美,所以我打算整理一次SQL的优化问题. 要知道怎么优化首先要知道一条SQL是怎么 ...

  9. python 连接 SQL Server 数据库

    #!/usr/bin/python # -*- coding:utf-8 -*- import pymssql import pyodbc host = '127.0.0.1:1433' user = ...

  10. mysql主丛之基于binlog的不停业务配置主从

    一 环境准备 主:192.168.132.121 从:192.168.132.122 主的数据库上面已经有数据,而且还在不断的写入 mysql> select * from darren.tes ...