一、成员函数的调用方式
1.Nonstatic member functions
C++的设计准则之一就是:nonstatic member function 至少必须和一般的nonmember function有相同的效率。而实际上,编译器内部会将“member函数实体”转换为对等的“nonmember函数实体”。其转化步骤大致如下:
- 改写函数的signature(函数名称+参数数目+参数类型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以调用该函数。该额外参数被称为this指针。
- 将每一个“对nonstatic data member”的存取操作“改为经由this指针来存取”。
- 对member function重新写成一个外部函数。对函数名称进行“mangling”处理,使它在程序中成为独一无二的语汇(比如将参数 类型和返回值类型也编码进来)。
2.Virtual member functions
如果normalize()是一个virtual member function,那么以下调用:
ptr->normalize();
将会被内部转化为:
( * ptr->vptr[ 1 ])( ptr );
其中:
- vptr表示由编译器产生的指针,指向virtual table。它被安插在每一个“声明有(或继承自)一个或多个virtual functions”的class object中。事实上其名称也会被“mangled”,因为在一个复杂的class派生体系中,可能存在多个vptrs。
- 1是virtual table slot的索引值,关联到normalize()函数。
- 第二个ptr表示this指针。
“经由一个class object调用一个virtual function”,这种操作应该总是被编译器像对待一般的nonstatic member function一样加以决议。如果在normalize()函数中再调用一个ptr的virtual member function,则这个function是以“class object调用”的方式进行决议的,即压制了由于虚拟机制而产生的不必要的重复调用操作。
3.Static member functions
静态成员函数有几个明显的特性是:
- 没有this指针。
- 不能够直接存取其class中的nonstatic members。
- 不能够被声明为 const、volatile或virtual。
- 不需要经由 class object 才被调用 ——虽然大部分时候它是这样被调用的。
一个static member function,理所当然会被提出class声明之外,并给予一个经过“mangled”的适当名称。由于static member function没有this指针,所以其地址的类型并不是一个“指向class member function的指针”,而是一个“nonmember 函数指针”。也就是说:
对于
unsigned int Point3d::object_count()
{
//....
}
&Point3d::object_count();
会得到一个数值,类型是:
unsigned int (*) ();
而不是:
unsigned int ( Point3d::*) ();
由于static member function缺乏this指针,因此差不多等同于nonmember function。
二、关于Virtual member functions
为了支持virtual function机制,必须首先能够对于多态对象有某种形式的“执行期类型判断法”。也就是说,以下的调用操作将需要ptr在执行期的某些相关信息
ptr->z();
如此一来才能够找到并调用z()的适当实体。
在C++中, virtual functions(可经同其class object被调用)可以在编译时期获知,此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌握,不需要执行期的任何介入。
一个class只会有一个virtual table。每一个table内含其对应的 class object中所有active virtual functions函数实体的地址。这些active virtual functions包括:
- 这个class所定义的函数实体,它会override一个可能存在的base class virtual function函数实体。
- 继承自base class的函数实体,这是在derived class不override base class virtual function时才会出现的情况。
- 一个pure_virtual_called()函数实体,它既可以扮演pure virtual function的空间保卫者角色,也可以当做执行期异常处理函数(有时候会用到)。
对于
ptr->z();
如何有足够的知道在编译时期设定virtual function的调用呢?
- 从指针是无法知道指针指向对象的类型的,但是可以通过ptr可以获取到对象的virtual table,从而可以通过virtual table的第一个slot(索引0)获得对象的类型信息。
- 即使不知道哪个z()函数实体会被调用,但是每一个z()函数地址都被放在slot4,这个是在编译时期就已经确定的了。
对于多重继承下的Virtual Functions,只需要注意其有n-1个vptr指针,n表示其上一层base classes的数目(因此,单一继承将不会有额外的virtual tables)。在基类和父类的函数调用上,有时候需要编译器介入进行必要的this指针调整,以指向到正确的base class的vptr上。