前言
Simplifier是编译器的一部分,处于type checking和code generation之间。它用来转换内部的程序表现。有3种转换是任何对象模型都需要的:
与编译器息息相关的转换(
Implementation-dependent transformations)例如,当
parser看到这个表达式:1
fct();
它并不知道是否(
a)这是一个函数调用操作,或者(b)这是overloaded calloperator在class object fct上的一种应用。默认情况下,这个式子所代表的是一个函数调用,但是当(b)的情况出现,Simplifier就要重写并调换call subtree。语言语意转换(
Language semantics transformations)这包括
constructor/destructor的合成和扩展、memberwise初始化、对于memberwise copy的支持、在程序代码中安插conversion operators、临时性对象,以及对constructor/destructor的调用。程序代码和对象模型的转换(
Code and object model transformations)这包括对
virtual functions、virtual base class和inheritance的一般支持、new和delete运算符、class objects所组成的数组、local static class instances、带有非常量表达式(nonconstant cxpression)之global object的静态初始化操作。
什么是C++对象模型?
- 语言中直接支持面向对象程序设计的部分
- 对于各种支持的底层实现机制
第1章:关于对象
在C语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。
C++在布局以及存取时间上主要的额外负担是由virtual引起,包括:
virtual function机制——用以支持一个有效率的“执行期绑定”virtual base class——用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实体”- 此外,还有一些多重继承下的额外负担,发生在“一个
derived class和其第二或后继之base class的转换”之间。
1.1 C++对象模型
在C++中,有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual。
在C++对象模型中,nonstatic data members被配置于每一个class object之内,static data members则被存放在所有的class object之外。static和nonstatic function members也被放在所有的class object之外。virtual functions则以两个步骤支持之:
- 每一个
class产生出一堆指向virtual functions的指针,放在表格之中。这个表格被称为vtbl。 - 每一个
class object被添加了一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)由每一个class的constructor、destructor和copy assignment运算符自动完成。
在虚拟继承的情况下,base class不管在继承串链中被派生(derived)多少次,永远只会存在一个实体(称为subobject)。
C++最初采用的继承模型并不运用任何间接性: base class subobject的data members被直接放置于derived class object中。这提供了对base class members最紧凑而且最有效率的存取。缺点就是: base class members的任何改变,包括增加﹑移除或改变类型等等,都使得所有用到“此base class或其derived class之objects”者重新编译。
virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针。
对象模型如何影响程序?
不同的对象模型,会导致“现有的程序代码必须修改”以及“必须加人新的程序代码”两个结果。例如下面这个函数,其中class X定义了一个copy constructor,一个virtual destructor,和一个virtual function foo:
1 | X foobar() |
这个函数有可能在内部被转化为:
1 | void foobar(X& _result) |
1.2 关键词所带来的差异
- 掌握
struct和class关键字的差异
struct关键词的使用实现了C的数据萃取概念,而class关键词实现的是C++的ADT(Abstract Data Type)概念。
C程序员的巧计(C++中不可用)。例如把单一元素的数组放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组:
1 | struct mumble |
C++中凡处于同一个access section的数据,必定保证以其声明次序出现在内存布局当中。然而被放置在多个access sections中的各笔数据,排列次序就不一定了。组合(composition),而非继承,才是把C和C++结合在一起的唯一可行方法(conversion运算符提供了一个十分便利的萃取方法):
1 | struct C_point { ... }; |
C struct在C++中的一个合理用途,是当你要传递“一个复杂的class object的全部或部分”到某个C函数中去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。然而这项保证只在组合的情况下才存在。
1.3 对象的差异
C++支持3种程序设计模型:
- 程序模型(面向过程)
- 抽象数据类型模型(基于对象模型)(封装)
- 面向对象模型(继承、多态)
在C++,多态只存在于一个个的public class体系中。nonpublic的派生行为以及类型为void*的指针可以说是多态,但它们并没有被语言明白地支持,也就是说它们必须由程序员通过明白的转型操作来管理。C++以下列方法支持多态:
经由一组隐含的转化操作。例如把一个
derived class指针转化为一个指向其public base type的指针1
Shape *ps = new Circle();
经由
virtual function机制经由
dynamic_cast和typeid运算符1
2if(Circle* pc = dynamic_cast<Circle*>(ps))
...
virtual function机制不只使得“当类型有所增加、修改、或删减时,我们的程序代码不需改变”。而且也使一个新的subtype的供应者不需要重新写出“对继承体系中的所有类型都共通”的行为和操作。
需要多少内存才能够表现一个class object?
nonstatic data members的总和大小- 加上任何
alignment(内存对齐) - 加上为了支持
virtual(function,base class)而由内部产生的额外负担
转型(cast)其实是一种编译器指令。大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式。
一个Base指针pb和一个Derived指针pd有什么不同?
1 | Derived d; |
它们每个都指向Base object的第一个byte。其间的差别是,pd所涵盖的地址包含整个Derived object,而pb所涵盖的地址只包含Derived object中的Base subobject。
除了Base subobject中出现的members,你不能使用pb来直接处理Derived的任何members。例外是通过virtual机制或转型操作。
当一个base class object被直接初始化为(或是被指定为)一个derived class object时,derived object就会被切割,以塞人较小的base type内存中,derived type将没有留下任何蛛丝马迹。
下面这一组定义,其可能的内存布局为:
1 | ZooAnimal za; |