概述
在实际生产中,遇到一个复杂的类,如果能看出这个类的内存模型结构,那么以后的操作基本就没有难度的;
所以说,学会分析一个类的内存模型,是每一个C++程序员必须要会的知识。
下面,就让我们来了解C++对象模型!
C++类封装和C中的结构体的区别
C++的类封装是在C语言中的结构体的基础上构建起来的,C结构体只允许存在数据,而不会存在对数据的操作。
C++语言中延承C语言中的结构体,但增加的对数据的操作,即成员函数;
类是对结构体的进一步封装,使某些数据成员对外不可见,称为私有成员。
类和结构体最大的区别就是:结构体成员均是public类型的。
那么,类和结构体的布局成本有没有区别呢?
对于只有数据成员的类和结构体在内存的布局是相同的,没有增加成本。
而members functions 虽然含在class的声明之内,却不出现在object之中。每一个 non-inline member fuction 只会诞生一个函数实体,调用时链接至函数体。至于每一个"拥有零个或一个定义"的 inline function 则会在其每一个调用者(模块)身上产生一个函数实体。
在C++中,有两种 class data members: static 和 nonstatic,以及三种 class member functions : static、nonstatic 和 virtual。
在C++对象模型中,非静态数据成员被配置在每一个 class object 之内,静态数据成员则被存在个别的 class object 之外,静态和非静态的成员函数也被放在个别的 class object 之外。
C++在布局及存取时间上主要的额外负担是由 virtual 引起。包括:
- virtual function 机制:用以支持一个有效率的"执行期绑定"
- virtual base class 用以实现"多次出现在集成体系中的base class,有一个单一而被共享的实体。
虚函数以及虚函数表
无论是含有虚函数还是虚拟继承,类都会产生一个虚函数表,同时每一个对象都会含有一个Vptr指针指向虚拟函数表。
虚拟函数的实现
1.每一个 class 产生一堆指向虚函数的指针,放在表格之中,这个表格被称为 virtual table(vtbl);
2.每一个 class object 被安插一个指针,指向相关的 virtual table。这个指针通常被称为 vptr。vptr 的设定和重置都由每一个类的构造函数、析构函数和赋值操作符重载函数自动完成且一旦完成初始化不能修 改。每一个类所关联的type_info object(主要用于RTTI)也经由 virtual table被指出来,通常放在表格的第一个slot。
多重继承模型
C++最初采用的继承模型时间基类对象中的数据成员直接存放在继承类对象内存中,包括基类对象的虚函数表和虚指针(貌似现在也是这样的模型),这样就可以对基类成员进行最有效率的存取,然而缺点是如果基类成员有任何的修改将会导致基类对象和继承类对象的重新编译。
有人提出一种间接存取模型:在继承类对象中放置一指针Bptr,该指针指向继承类的基类表,类似与虚函数表,基类表中每一项都指向继承类对象的基类 对象。这样的好处是,基类对象的任何改变吗都不会影响到继承类对象的内存布局,缺点是导致对基类对象成员的存取效率低,随着继承深度的增加,间接性越强, 效率越低。
因此又有人提出,在继承类对象中放置与基类相等数目的指针指向每个基类,这样存取时间就恒久不变了,但是这样无疑增加了继承类的内存开销。
目前使用的多重继承模型仍为第一种,继承类对象中含有所有基类对象数据成员和虚指针,同时继承基类的虚函数表共享为继承类的虚函数表。
虚拟继承模型
虚拟继承保证继承类中只含有一个基类对象,继承类中会产生虚指针指向虚表中offset指向继承类中的基类对象。只有直接虚继承的继承类对象才会产生Vptr。
目前,关于虚拟继承的继承类的内存布局有三种模型:
1、原始模型:在每个继承类对象中设置指针,用以指向继承类中的基类对象。优点:通过指针能够保证继承类中只有一个基类对象,可以直接存取基类对象。缺点:若继承类有多个虚拟基类,则需要多个指针指向基类们,会消耗较多的内存。
2、有人提出像虚函数表一样,提供一个虚拟基类表virtual base class table,表中每个slot指向一个基类对象的地址。在继承类对象中,设置一个虚拟基类表指针指向基类表。优点:继承类减少了因虚拟基类指针而产生的开 销,且继承类不会因为虚拟基类的增加而改变内存模型。缺点:存取基类对象数据成员会导致效率低下。
3、现在最常用的模型:扩充原Virtual table,在虚表负offset位置指向虚基类对象,因此,只要是虚继承就会产生虚指针和虚表。
虚拟多继承,采用“钻石”继承结构,其每个类的内存分布如下图:其中虚指针的位置可以在顶部或尾部。
上述内容是类对象内存模型的介绍:
(1)无虚函数和虚继承:nostatic数据成员存放在对象中,static数据成员存放在对象外属于整个类。nostatic和static函数成员均放在对象外。
(2)有虚函数和虚继承:virtual函数放在虚函数表中,通过虚指针进行查找调用。基类对象数据成员和虚指针均存放在继承类的对象中。