1. 调用方式
经由一个class object调用一个virtual function,这种操作应该总是被编译器像对待一般的nonstatic member function一样的加以决议.
// Point3d obj
obj.normalize();
virtual void normalize() {... }
2. static member function
((Point3d*)0)->object_count(); // object_count是一个静态成员函数
实际上,只有当一个或多个nonstatic data member在member function中直接存取时,才需要class object。class object提供了this 指针给这种形式的函数调用使用。如果在member function没有存取任何数据成员,那么也就没必要用class object来调用这个成员函数了。
static member function的特性就是它没有this指针。所以就导致以下的特性:
1) 它不能存取class中的nonstatic members
2)它不能被声明为const,volatile或virtual
3)它不需要经由class object才被调用
如果取一个static member function的地址,获得的将是其在内存中位置,也就是它的地址。由于它并没有this指针,所以其地址的类型并不是一个“指向class member function的指针“。而是一个”nonmember 函数指针“。譬如:
&Point3d::object_count();
会得到一个数值,类型是
unsigned int (*)();
而不是:
unsigned int (Point3d::*)();
3. 多态
在C++中,多态表示“以一个pulibc base class的指针(或reference),寻址出一个derived class object"的意思。
关键词class和struct并不能帮助我们决定类是否有多态性,同时也没有导入polymorphic之类的新关键词,故识别一个class是否多态的唯一的方法就是看它是否有virtual function。如果有,则需要增加额外的执行期信息。
virtual function table中应该有什么样的内容呢?
1) 这个class所定义的函数实体。它会改写一个可能存在的base class virtual function函数实体。
2)继承自base class的函数实体。这是在derived class决定不改写virtual function时才会出现的情况。
3)一个pure_virtual_called()函数实体,它既可以扮演pure virtual function的空间保卫者角色,也可以当作执行期异常处理函数。
每一个virtual function都被指派一个
固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关联。
由最上层的基类的virtual function开始确定索引值,子类如果重写某个virtual function,则索引值不变,只是更改对应slot中的函数地址;子类如果增加更多的索引值,则append更多的slot。第0个slot存储是当前类的typeinfo。
4. nonstatic member function
取一个nonstatic member function的地址,如果该函数是nonvirtual,则得到的结果是它在内存中真正的地址。然而这个值是不完全的,它也需要被绑定于某个class object的地址上,才能够通过它调用该函数。所有的nonstatic member functions都需要对象的地址(强调,程序可否正常运行依赖于this指针在函数内部有没被用来获取它内部数据)。
如果这个nonstatic member function是virtual,处理上会有些那些不一样的地方呢?
float (Point::*pmf)() = &Point::z;
Point* ptr = new Point3d;
如果以ptr来调用它的z()函数,我们知道它最终会调用Point3d::z()函数,这是多态所带来的效果。
如果以ptr和pmf间接调用z()函数,仍然能够调用到Point3d::z()函数吗,答案是可以。
面对一个virtual nonstatic member function,其地址在编译时期是未知的(因为虚函数多态调用是在运行期决议的),所能知道的仅是virtual function在其相关之virtual table的索引值。也就是说,
对一个virtual function取地址,所能获得的是一个索引值。
所以上面的z()如果是virtual,那么pmf将会是一个索引值。编译器会将其转化为下面类似的式子:
(*
ptr->vptr[
(int)pmf])(ptr);
如果z()不是virtual,那么调用式子将会是:
(ptr->*pmf)(); => (*pmf)(ptr);