前言
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; |
