C++虚继承的实现原理由内存布局开始讲起

目录
  • 准备工作
  • 虚继承的内存分布情况

准备工作

1、VS2012使用命令行选项查看对象的内存布局

微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout。使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可。切换到cpp文件所在目录下输入如下的命令即可

c1 [filename].cpp /d1reportSingleClassLayout[className]

其中[filename].cpp就是我们想要查看的class所在的cpp文件,[className]指我们想要查看的class的类名。(下面举例说明...)

2、查看普通多继承子类的内存布局

既然我们今天讲的是虚基类和虚继承,我们就先用上面介绍的命令提示工具查看一下普通多继承子类的内存布局,可以跟后文虚继承子类的内存布局情况加以比较。

我们新建一个名叫NormalInheritance的cpp文件,输入一下内容。

/**
	普通继承(没有使用虚基类)
*/
// 基类A
class A
{
public:
	int dataA;
};
class B : public A
{
public:
	int dataB;
};
class C : public A
{
public:
	int dataC;
};
class D : public B, public C
{
public:
	int dataD;
};

上面是一个简单的多继承例子,我们启动Visual Studio命令提示功能,切换到NormalInheritance.cpp文件所在目录,输入一下命令:

c1 NormalInheritance.cpp /d1reportSingleClassLayoutD

我们可以看到class D的内存布局如下:

从类D的内存布局可以看到A派生出B和C,B和C中分别包含A的成员。再由B和C派生出D,此时D包含了B和C的成员。这样D中就总共出现了2个A成员。大家注意到左边的几个数字,这几个数字表明了D中各成员在D中排列的起始地址,D中的五个成员变量(B::dataA、dataB、C::dataA、dataC、dataD)各占用4个字节,sizeof(D) = 20。

为了跟后文加以比较,我们再来看看B和C的内存布局:

虚继承的内存分布情况

上面我们看到了普通多继承子类的内存分布情况,下面我们进入主题,来看看典型的菱形虚继承子类的内存分布情况。

我们新建一个名叫VirtualInheritance的cpp文件,输入一下内容:

/**
	虚继承(虚基类)
*/
#include <iostream>
// 基类A
class A
{
public:
	int dataA;
};
class B : virtual public A
{
public:
	int dataB;
};
class C : virtual public A
{
public:
	int dataC;
};
class D : public B, public C
{
public:
	int dataD;
};

VirtualInheritance.cpp和NormalInheritance.cpp的不同点在与C和C继承A时使用了virtual关键字,也就是虚继承。同样,我们看看B、C、D类的内存布局情况:

我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类类有很大的不一样。对于类B和C,sizeof的值变成了12,除了包含类A的成员变量dataA外还多了一个指针vbptr,类D除了继承B、C各自的成员变量dataB、dataA和自己的成员变量外,还有两个分别属于B、C的指针。

那么类D对象的内存布局就变成如下的样子:

  • vbptr:继承自父类B中的指针
  • int dataB:继承自父类B的成员变量
  • vbptr:继承自父类C的指针
  • int dataC:继承自父类C的成员变量
  • int dataD:D自己的成员变量
  • int A:继承自父类A的成员变量

显然,虚继承之所以能够实现在多重派生子类中只保存一份共有基类的拷贝,关键在于vbptr指针。那vbptr到底指的是什么?又是如何实现虚继承的呢?其实上面的类D内存布局图中已经给出答案:

实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚表(virtual table),虚表中记录了vbptr与本类的偏移地址;第二项是vbptr到共有基类元素之间的偏移量。在这个例子中,类B中的vbptr指向了虚表D::$vbtable@B@,虚表表明公共基类A的成员变量dataA距离类B开始处的位移为20,这样就找到了成员变量dataA,而虚继承也不用像普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。

为了进一步确定上面的想法是否正确,我们可以写一个简单的程序加以验证:

int main()
{
	D* d = new D;
	d->dataA = 10;
	d->dataB = 100;
	d->dataC = 1000;
	d->dataD = 10000;
	B* b = d; // 转化为基类B
	C* c = d; // 转化为基类C
	A* fromB = (B*) d;
	A* fromC = (C*) d;
	std::cout << "d address    : " << d << std::endl;
	std::cout << "b address    : " << b << std::endl;
	std::cout << "c address    : " << c << std::endl;
	std::cout << "fromB address: " << fromB << std::endl;
	std::cout << "fromC address: " << fromC << std::endl;
	std::cout << std::endl;
	std::cout << "vbptr address: " << (int*)d << std::endl;
	std::cout << "    [0] => " << *(int*)(*(int*)d) << std::endl;
	std::cout << "    [1] => " << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20
	std::cout << "dataB value  : " << *((int*)d + 1) << std::endl;
	std::cout << "vbptr address: " << ((int*)d + 2) << std::endl;
	std::cout << "    [0] => " << *(int*)(*((int*)d + 2)) << std::endl;
	std::cout << "    [1] => " << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12
	std::cout << "dataC value  : " << *((int*)d + 3) << std::endl;
	std::cout << "dataD value  : " << *((int*)d + 4) << std::endl;
	std::cout << "dataA value  : " << *((int*)d + 5) << std::endl;
}

得到结果为:

到此这篇关于C++虚继承的实现原理由内存布局开始讲起的文章就介绍到这了,更多相关C++虚继承内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C++中单继承与多继承的使用

    目录 前言 1.继承的概念和定义 (1)继承的概念 (2)继承的定义方法 (2)继承后子类的成员类型 2.基类与派生类的赋值转换 (1)派生类赋值给基类 (2)基类给派生类 3.继承中的作用域 (1)隐藏的概念 (2)例题 4.派生类的默认成员函数 (1)默认生成的成员函数 (2)自己写 5.友元与静态成员 6.多继承 (1)概念 (2)复杂的菱形继承 (3)虚继承解决菱形继承问题 (4)虚继承的原理 7.继承与组合 (1)两者区别 (2)继承与组合的区别 (3)使用情况 8.总结 前言 C++

  • C++ 超详细梳理继承的概念与使用

    目录 继承的概念及定义 继承的概念 继承定义 定义格式 继承关系和访问限定符 继承基类成员访问方式的变化 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 菱形继承 虚拟继承解决数据冗余和二义性的原理 继承的总结和反思 继承的概念及定义 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体

  • C++图文并茂讲解继承

    目录 一.生活中的例子 二.惊艳的继承 三.继承的意义 四.小结 一.生活中的例子 组合关系∶整体与部分的关系 下面看一个组合关系的描述代码: #include <iostream> #include <string> using namespace std; class Memory { public: Memory() { cout << "Memory()" << endl; } ~Memory() { cout <<

  • C++详细讲解继承与虚继承实现

    目录 继承的概念及定义 概念: 定义: 继承关系和访问限定符 总结 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 虚继承原理 继承的总结 继承的概念及定义 概念: 继承机制是面向对象程序设计为了提高代码复用率的一种手段,它可以保持原类特性的基础上进行拓展,简单来说继承是类层次的复用. 接下来我们来看一个简单的继承 class Person { public: void Print() { cout<<"nam

  • 关于C++对象继承中的内存布局示例详解

    前言 本文给大家介绍的是关于C++对象继承的内存布局的相关内容,分享出来供大家参考学习,在开始之前说明下,关于单继承和多继承的简单概念可参考此文章 以下编译环境均为:WIN32+VS2015 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数. 首先先通过一个例子来引入虚函数表,假如现在有三

  • C++深入探究继承的概念与使用

    目录 1.概念及定义 1.1 概念 1.2 定义 2.class与struct的区别 3.赋值兼容规则 4.继承中的作用域问题 5.派生类(子类)的默认成员函数 5.1 构造函数 5.2 拷贝构造函数 5.3 赋值运算符重载 5.4 析构函数 6.基类中哪些成员被子类继承了 6.1 成员变量 6.2 成员方法 7.友元函数被继承了吗 1.概念及定义 1.1 概念 继承主要的工作就是-----共性抽取 具体地讲: ①继承机制是面向对象程序设计使代码可以复用的最重要的手段; ②允许程序员在保持原有类

  • 关于C++继承你可能会忽视的点

    目录 前言 一.什么是继承 二.基类与派生类的赋值转换 2.1天然支持的理解 三.继承当中的作用域 四.派生类的默认构造成员函数 4.0什么时候需要写6个默认成员函数 4.1构造函数 4.2拷贝构造 4.3赋值重载 五.菱形继承和菱形虚拟继承 5.1菱形继承 六.继承的总结 继承和组合 总结 前言 继承是使代码复用的一种重要的手段,我们在C语言时期写的swap函数逻辑,通常会单独写出来再给其他函数复用,这个继承可以理解成是类级别的一个复用,它允许我们在原有类的基础上进行扩展,增加新的功能. 一.

  • C++深入探究不同的继承体系

    目录 单继承 多继承 菱形继承 概念 存在的问题 解决方案 菱形虚拟继承 虚拟继承 什么是虚拟继承 内存层面理解虚拟继承 虚拟继承和普通单继承的区别 虚拟继承+菱形继承 声明: 本文的测试环境为Windows平台下的VS2013环境 单继承 一个子类只有一个父类.这类继承方式是最为简单的.具体可以参考继承 上篇中的例子.这里给出简单的图示即可: 多继承 一个子类有两个或两个以上的直接父类,这样的继承关系称之为多继承. 图解: 注意事项:  ①在多继承的场景下,建议在继承的每一个基类名称前都加上继

  • C++ 继承的范例讲解

    目录 1.继承的概念 2.继承方式 3.基类与派生类的赋值转换 4.作用域与隐藏 5.派生类的默认成员函数 6.友元与静态成员 7.菱形继承与虚继承 8.继承和组合 1.继承的概念 继承,是面向对象的三大特性之一.继承可以理解成是类级别的一个复用,它允许我们在原有类的基础上进行扩展,增加新的功能. 当创建一个类时,我们可以继承一个已有类的成员和方法,并且在原有的基础上进行提升,这个被继承的类叫做基类,而这个继承后新建的类叫做派生类. 用法如下: class [派生类名] : [继承类型] [基类

  • C++虚继承的实现原理由内存布局开始讲起

    目录 准备工作 虚继承的内存分布情况 准备工作 1.VS2012使用命令行选项查看对象的内存布局 微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout.使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可.切换到cpp文件所在目录下输入如下的命令即可 c1 [filename].cpp /d1reportSingleClassLayout[className] 其中[fi

  • 关于C++虚继承的内存模型问题

    1.前言 C++虚继承的内存模型是一个经典的问题,其具体实现依赖于编译器,可能会出现较大差异,但原理和最终的目的是大体相同的.本文将对g++中虚继承的内存模型进行详细解析. 2.多继承存在的问题 C++的多继承是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员.从概念上来讲这是非常简单的,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个,比如典型的是菱形继承,如图2-1所示: 图2-1 菱形继承 在图2-1中,类A派生出类B和类C,类D继承自类

  • C++ 继承,虚继承(内存结构)详解

    目录 普通的公有继承 多重继承 虚继承 虚继承(菱形继承) 总结 普通的公有继承 class test1 { public: test1(int i) :num1(i) {} private: int num1; }; class test2 : public test1 { public: test2(int i,int j) : test1(i), num2(j) { } private: int num2; }; void main() { test2 t2(1,2); } (test2内

  • 详谈C++中虚基类在派生类中的内存布局

    今天重温C++的知识,当看到虚基类这点的时候,那时候也没有太过追究,就是知道虚基类是消除了类继承之间的二义性问题而已,可是很是好奇,它是怎么消除的,内存布局是怎么分配的呢?于是就深入研究了一下,具体的原理如下所示: 在C++中,obj是一个类的对象,p是指向obj的指针,该类里面有个数据成员mem,请问obj.mem和p->mem在实现和效率上有什么不同. 答案是:只有一种情况下才有重大差异,该情况必须满足以下3个条件: (1).obj 是一个虚拟继承的派生类的对象 (2).mem是从虚拟基类派

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • C++虚函数表的原理与使用解析

    目录 前言 1.虚函数表 2.一般继承(无虚函数覆盖) 3.一般继承(有虚函数覆盖) 4.多重继承(无虚函数覆盖) 5.多重继承(有虚函数覆盖) 6.安全性 6.1 通过父类型的指针访问子类自己的虚函数 6.2 访问non-public的虚函数 7.结束语 7.1 VC中查看虚函数表 7.2 例程 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛

  • C++虚函数表与类的内存分布深入分析理解

    目录 不可定义为虚函数的函数 将析构函数定义为虚函数的作用 虚函数表原理 继承关系中虚函数表结构 多重继承的虚函数表 多态调用原理 对齐和补齐规则 为什么要有对齐和补齐 资源链接 不可定义为虚函数的函数 类的静态函数和构造函数不可以定义为虚函数: 静态函数的目的是通过类名+函数名访问类的static变量,或者通过对象调用staic函数实现对static成员变量的读写,要求内存中只有一份数据.而虚函数在子类中重写,并且通过多态机制实现动态调用,在内存中需要保存不同的重写版本. 构造函数的作用是构造

  • c++基础语法:虚继承

    虚继承 的概念的提出主要是为了解决C++多继承的问题,举个最简单的例子: 复制代码 代码如下: class animal{        public :              void op()                  {cout << "hello animal" ;} };class tiger : public animal {        public :              void tg()                  {cout

随机推荐