title: “ 《深度探索C++对象模型》笔记(1)\t\t”
tags:

  • 笔记
    url: 1410.html
    id: 1410
    categories:
  • C/C++
    date: 2018-12-25 11:40:12

介绍

正如前几篇面试记录中各位面试官说的,了解如何使用还要明白一下其背后的原理,确实自己的知识欠缺太多,只是浮于表面,阅读本书来一次深度探索,将以往不知道的、模棱两可的尽可能的清晰化。话说科班的人好多都是找工作之前就看,而我是之后才开始,虽然晚了,但庆幸已经觉悟了。 马上2019,准备春招,加油

第一章 关于对象

读后感放前面

经过阅读以后,感觉本质面向对象语言就是人机交流的一种语言,而C主要做的是将通过一系列关键词、自定义名称、符号构成的代码,转换成内存中的存储方式,实质就是面向对象这个哲学概念如何用内存中的布局、存储方式表示以及在代码中使用继承、多态等概念时是如何通过内存中的数据找到实际要运行的代码。 而这一章重点在说面向对象这个概念在C这个语言中是如何实现的内存布局、存储,(当然还有不同编译器相应实现的差异)

OO,OB,virtual function,virtual base class

Object-Oriented OO面向对象—我理解的:封装、继承、多态 Object-Based OB基于对象—我理解的:只有封装 在传统的C程序中,采用的是过程式的思维:“数据”和“处理数据的操作(函数)”是分开来声明的,它们二者之间并没有关联性。但到了C里,倾向于采用独立的“抽象数据类型”(ADT)来实现数据的封装。从代码结构上来看,C似乎比C要消耗更高的时间和空间成本,但事实未必如此。相比于C,C在布局和时间上主要的额外负担是由virtual引起的,这分为两部分:(非virtual,在布局和时间C相比于C并没有太大的负担) 1.virtual function机制,用以支持一个有效率的“执行期绑定”,这里虚函数就是类声明中用的virtual void f(int a);这一类的声明 2.virtual base class,用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实例”。这个是虚继承class a:public b{},class c:virtual b{}

“抽象数据类型”(ADT)等程序设计范式(programming paradigms)

函数化程序设计、逻辑程序设计、语义数据模型、几何计算、数值计算、面向对象设计、原型设计、自然语言…… C++支持三种:程序模型(procedural model)C语言支持的、抽象数据类型模型(abstract data type model,ADT)、面向对象模型(Object-Oriented model)

对于抽象数据类型: 抽象数据类型(ADT)是一个实现包括储存数据元素的存储结构以及实现基本操作的算法。在这个数据抽象思想中,数据类型的定义和它的实现是分开的,这在软件设计中是一个重要的概念。这使得只研究和使用它的结构而不用考虑它的实现细节成为可能。ADT包括数据数据元素,数据关系以及相关的操作。 抽象数据类型需要通过固有数据类型(高级编程语言中已实现的数据类型)来实现。抽象数据类型是与表示无关的数据类型,是一个数据模型及定义在该模型上的一组运算。对一个抽象数据类型进行定义时,必须给出它的名字及各运算的运算符名,即函数名,并且规定这些函数的参数性质。一旦定义了一个抽象数据类型及具体实现,程序设计中就可以像使用基本数据类型那样,十分方便地使用抽象数据类型。 比如C++中,string类通过对+、等号操作符重载,实现对字符串的拼接、相等性判断。而C中需要strXXX(a,b)

C++对象模型

书里介绍了三种的C对象模式:简单对象模型、表格驱动对象模型、C对象模型。 简单对象模型:一个object(对象)表是一系列的slots(表格中的一格,一个条目,内存中一块地方),每个slot指向一个成员,成员按照声明顺序排列,成员包括数据成员和函数成员。这种方式object的存储区域只存放了一系列的“指针”分别指向每个member的位置。不把members直接存到object存储区域是为了避免不同member有不同的内存占用,而用“指针”的形势可以保证object的slots大小确定,并可通过索引确定每个member。这个模型并未使用,但保留了“指向成员的指针”观念。

这样的设计很简单,每个对象建立(实例化)的时候,根据class的声明可以快速建立出一个表,表内根据声明顺序存所有的函数、数据成员。但是对于一个class实例化两次应该就要建立两份,对于继承关系来说,子类的实例化后的slots中应该也重复包含了父类的slots。保证来设计简单,但在空间上、执行期效率上低。 执行期效率低:我想的是,多态的时候,子类用父类表示,那么父类没有子类那么多的member,需要重新建立一个映射索引?否则父类的pt[5]是第五个成员函数,但在子类的表中可能父类第五个成员函数已经成为的第8个,中间加了3个新的成员。而多重继承、虚函数的重写、出现继承串链将需要很多复杂操作。

表格驱动对象模型:一个object表只有两项分别指向两个表,一个指向Function Member Table存放所有成员函数的地址(只存地址),一个指向Member Data Table存放所有数据成员的实际值(存值)。这个模型未被使用,但将数据与函数分表存储的观念被保留。

应该也没解决有关多态的方式,对于成员函数表和简单对象模型是一样的,所以没有使用。

C++对象模型中:一个object表中有两类内容,第一类是所有的nonstatic data members非静态数据成员的值(注意是值)直接存在表中,非静态的所有每个实例之间不共用(如果有int a;这样的成员,那么存储的也是值,因为指针也是个值,值的类型就是(int)),第二类是指针,一个指针指向一个虚函数表(virtual table,vtbl),这个指针叫(vptr),vptr的设定和重置由constructor、destructor以及copy assignment运算符自动完成。 对于静态数据成员、非静态/静态成员函数放在object表外侧。 虚函数表中存储所有虚函数的指针,每一个class所关联的type_info object(用以支持runtime type identification,RTTI)也存放在virtual table之中一般放在vtbl的第一个slot。

静态数据成员、非静态/静态成员函数:这几个都是同一个class中不同实例共用的,且不会在运行时改变(指向这些的指针不变),所以把他们放在了object外吧。对于静态数据成员,实际上是存放在全局区的(未初始化前在全局区临近区域)。这样object table中就不用建立指向他们的指针了,因为在class的内存区域(class声明)中已经有了这些成员的具体位置,但是这应该也会导致一次跳转,class声明的内存中找对应的指针。 type_info object,运行时特征,应该是永久记录着这个对象在构建时是什么类型吧,这样在多态变化过程中也可以做到“不忘初心”,猜想:在通过dynamic_cast进行向下转型时的安全检查可能就是查看这个吧。

该模型的优点是,它的空间和存取时间的效率较高;缺点:如果应用程序本身的代码未改变,而所用到的class object的nonstatic data members有所修改,那么应用程序的代码同样需要重新编译(因为内存分布发生了改变)。同时也付出了空间和执行效率的代价(通过vptr先跳转到vtbl然后才能找到virtual,对于静态数据成员、非静态/静态成员函数我认为也是通过指针找到的,因为没有直接存在object table中,也就是只有nonstatic data members是直接访问,其他都要跳一次)

继承

public继承,对于出现多态(对virtual function重写),修改vtbl中的指针即可。 虚拟继承:不管base class在继承串链中被派生多少次,永远只会存在一个实例。如iostream中,iostream多重继承了ostream/istream,ostream/istream虚拟继承了ios,所以实例化iostream时只有一个ios实例。

也就是说每个object中还存了一个virtual base class table的表?或者或有一个表存了所有父类object的指针,这样才能避免重复的占用空间,也就导致了(“间接性”的级数将因为继承的深度而增加)

只有通过pointer、reference方式才可支持多态: class B:classA {} A a;//a是A B b;//b是B A c=b;//b被改成A类型,内容也是只有A了,不会在有B的内容。B重写A的方法,用c是调A的方法,因为不是引用或指针。译注:这会产生sliced切割。b原始是B类型占用空间大于c的A类型空间,多出的内容会被舍弃,但一个object的存储是先存储base object,逐级存储后续object,因此并不影响c调用A的方法。 A &d=b;//d还是B类型,调用还是B类型的方法内容,但无法调用B特有的方法,因为当做A类型来用了 A *e=b;//同上

class object有多大

注意,这是说一个对象的大小,不是class声明的大小,对于静态数据成员、非静态/静态成员函数是存在object外的,指向他们的指针也不需要存在object中,可以在class声明区域填写指针,因为这些指针的指向不会变。 大小包括:非静态数据成员的总和(对于指针类型数据成员,只是指针的大小)+对齐补位的空间+为支持virtual的空间