读起C++对象模型,有点吃力。真的很吃力。。
P140
C++支持三种类型的成员函数:static,nonstatic,virtual.
P141
C++的设计准则之一就是:nonstaic member function至少必须和一般的nonmember function有相同的效率。在设计上为了支持这一准则,编译器内部已将"member 函数实体"转换为对等的"nonmember 函数实体"。下面是转化的步骤
1. 改写函数的signature(意指函数原型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以调用该函数。该额外参数被称为this指针。
Ex1:
//non-const nonstatic member function
Point3d Point3d::magnitude()
>> Point3d Point3d::magnitued(Point3d *const this)
//其中的const意思是this指针不能指向其他的地址。
Ex2: //const nonstatic member function
Point3d Point3d::magnitude()const
>> Point3d Point3d::magnitude(const Point3d *const this)
//其中第一个const的意思是 this 指针指向的Point3d对象的内容不能变更
//其中第二个const的意思是 this 指针不能指向其他的地址
2. 将每一个"对 nonstatic data member 的存取操作"改为经由 this 指针来存取:
Ex:
{
return sqrt( this->_x * this->_x +
this->_y * this->_y +
this->_z * this->z);
}
3. 将 member function 重新写成一个外部函数。对函数名称进行mangling,使它在程序中成为独一无二的词汇:
name-mangling的设计,避免某些函数重载或者重写时候的名字重合的尴尬。一般用signature来标记即函数名称+参数数目+参数类型。当然咱们编译器错误消息用的是未经mangled的,而链接器用的是mangled过内部名称。
Ex:
extern magnitude__7Point3dFv(register Point3d *const this);
这样函数的转化就完成了,而对每一个调用操作也都必须转换。于是:
obj.magnitude() >> magnitude__7Point3dFv(&obj);
而
ptr->magnitude() >> magnitude__7Point3dFv(ptr);
P144 由于 member function 可以被重载(overloaded),所以需要广泛的mangling手法,以提供独一无二的名称。但是如果声明 extern "C",就会压抑 nonmemeber functions的"mangling"效果.
P147
如果normalize()是一个virtual member function,那么以下的调用:
ptr->normalize();
将会被内部转化为:
(*ptr->vptr[1])(ptr);
其中:
- vptr表示由编译器产生的指针,指向virtual table。它被安插在每一个"声明有(或继承自)一个或多个virtual function"的class object中。事实上其名称也会被"mangled",因为在一个复杂的class派生体系中,可能存在有多个vptrs。
- 1是virtual table slot的索引值,关联到normalize()函数。
- 第二个ptr表示this指针。
P147
使用 class scope operator 明确调用一个 virtual function,其决议(resolved)方式会和 nonstatic member function一样。
P148
对于以下调用:
// Point3d obj;
obj.normalize()
如果编译器把它转换为:
(*obj.vptr[1])(&obj);
虽然语法正确,却没有必要。因为经由obj调用的函数实体只可以是Point3d::normalize()。所以"经由一个class object调用一个virtual function",这种操作应该总是被编译器像对待一般的nonstatic member function 一样地加以决议(resolved)
这项优化工程的另一利益是,virtual function的一个inline函数实体可以被扩展(expanded)开来,因而提供极大的效率利益。
P150
Static member function的主要特性是它没有this指针。以下的次要特性都源于其主要特性:
- 它不能够直接存取其class中的nonstatic members。
- 它不能被声明为const,volatile,virtual。
- 它不需要经由class object才被调用-虽然大部分时候它是这样被调用的。
"member selection"语法的使用是一种符号上的便利,它会被转化为一个直接的调用操作:
if(Point3d::object_count()>1)
如果class object是因为某个表达式而获得的,会如何呢?例如:
if(foo().object_count()>1)
这个表达式仍然需要被评估求值(evaluated):
//这样做的目的是为了保存副作用
//上面的代码被转化为如下形式
(void)foo();
if(Point3d::object_count()>1)
P151
如果取一个static member function的地址,获得的将是其在内存中的位置,也就是其地址。由于static member function没有this指针,所以其地址的类型并不是一个"指向class member function的指针",而是一个"nonmember 函数指针"。
Ex
&Point3d::object_count();
会得到一个数值,其类型是:
unsigned int(*) ();
而不是
unsigned int (Point3d::*)();
P151
Static member function由于缺乏this指针,因此差不多等同于nonmember function。它提供了一个意想不到的好处:成为一个callback函数,使我们得以将C++和C-based X Windows系统结合。它们也可以成功地应用到线程(thread)函数身上。
P152
Virtual function的一般实现模型:每一个class有一个virtual table,内含该class之中有作用的virtual function的地址,然后每个object有一个vptr,指向virtual table的所在。
为了支持virtual function机制,必须首先能够对于多态对象有某种形式的“执行期类型判断法”(runtime type resolution)。
P152
在C++中,多态(polymorphism)表示“以一个public base class的指针(或reference),寻址出一个derived class object”的意思。
P154
识别一个class是否支持多态,唯一的方法就是看看它是否有任何virtual function。
只要class拥有一个virtual function,它就需要这份额外的执行期信息。
我们所需要的执行期信息包括:
1)ptr所指对象的真实类型。这可使我们选择正确的virtual function实体。
2)virtual function实体位置,以便我们能够调用它。
P155
首先我们需要在每一个多态的class object身上增加两个members
1)一个字符串或数字,表示class的类型
2)一个指针,指向某表格,表格中带有程序的virtual function的执行期地址。
表格中的virtual function地址如何被构建起来?在C++中,virtual function(可经由其class object被调用)可以在编译时期获知,此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌握,不需要执行的任何介入。
执行期备妥这些函数地址,只完成了一半的工作。另一半就是找到这些地址。
1)为了找到表格,每一个class object被安插上一个由编译器内部产生的指针,指向该表格。
2)为了找到函数地址,每一个virtual function被指派一个表格索引值。
这些工作都由编译器完成,执行期要做的,只是在特定的virtual table solt(记录着virtual function的地址)中激活virtual function。
P155
一个class只会有一个virtual table。每一个table内含其对应的class object中所有的active virtual function函数实体的地址。这些active virtual function包括:
- 这个class所定义的函数实体。它会改写(overriding)一个可能存在的base class virtual function函数实体。
- 继承自base class的函数实体。这是在derived class决定不改写virtual function时才会出现的情况。
- 一个pure_virtual_call()函数实体,它既可以扮演pure virtual function的空间保卫者角色,也可以当作执行期异常处理函数(有时候会用到)。
P156
每一个virtual function都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关联。
P158
当继承发生时,有下面三种情况会发生:
1)它可以继承base class所声明的virtual function的函数实体。正确地说,该函数实体的地址会被拷贝到derived class的virtual function相对应的slot之中。
2)它可以使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。
3)它可以加入一个新的virtual function。这时候virtual function的尺寸会增大一个slot,而新的函数实体地址会被放进该slot之中。
P159
现在,如果有这样的式子
ptr->z();
那么,我们如何有足够的知识在编译时期设定virtual function的调用呢?
1) 一般而言,我们并不知道ptr所指对象的真正类型,然而我们知道,经由ptr可以存取到该对象的virtual table
2) 虽然我们不知道哪一个z()函数实体会被调用,但我们知道每一个z()函数地址都被放在slot4
这些信息使得编译器可以将该调用转化为:
(*ptr->vptr[4])(ptr);
在这个转化中,vptr表示编译器所安插的指针,指向virtual table,4表示z()被赋值的slot编号(关联到类继承体系的virtual table)。唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体。
在一个单一继承体系中,virtual function机制的行为十分良好,不但有效率而且很容易塑造出模型。
P174
指向 Member Function 的指针
取一个 nonstatic data member 的地址,得到的结果是该member在class布局中的bytes位置(再加1)。它是一个不完整的值,需要被绑定于某个class object的地址上才能够被存取。
取一个nonstatic member function 的地址。如果该函数是 nonvirtual, 则得到的结果是它在内存中真正的地址。这个值也是不完全的。它需要被绑定于某个class object的地址上,才能够通过它调用该函数。 所有的 nonstatic member functions 都需要对象的地址(以参数this指出)。