C++的设计准则之一:nonstatic member function至少和一般的nonmember function有相同的效率。
4.1 Member的各种调用方式
- 非静态成员函数:内化为nonmember function。1.安插this指针 2.重写为一个外部函数,进行mangling处理
- 虚拟成员函数:基类的指针调用派生类的虚函数——通过虚表寻址调用;非指针对象直接调用虚函数——同nonstatic member function一样; 函数内部调用虚函数——同nonstatic member function一样(压制虚拟机制)。
- 静态成员函数:没有this指针。主要特性:1.不能直接访问nonstatic members; 2.不能被声明为const、volatile、virtual; 3.作为callback函数,提供封装性(否则只能作为全局函数才能实现回调)。
4.2 Virtual Member Functions
实现模型:每一个class 只会有一个virtual table,内含该class之中有作用的virtual function的地址,每一个object有一个vptr(多继承可能有多个),指向virtual table的所在。
虚函数的地址以及存放虚函数地址的虚表在编译时期就确定下来了,不需要执行期的介入。执行期要做的是,激活虚函数。
(一)单一继承
一个类只会有一个virtual table,表内含有所有active virtual functions函数实体的地址。active virtual functions包括:
- 这个类所定义的函数实体,它会改写一个可能存在的base virtual functions函数实体地址。
- 继承自基类,但派生类决定不该写的虚函数。
- 纯虚函数地址。
单一继承的原理:
- 继承基类的虚函数实体:基类所有虚函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。
- 重写基类的虚函数实体:将自己的函数实体地址放到虚表对应的slot中。
- 新加入的virtual function:virtual table的尺寸增大一个slot,新的函数实体地址放进该slot之中。
多态调用的原理:
基类指针ptr指向派生类的对象,派生类的对象在构建时,会生成一个指向派生类虚表的虚指针,并设定初值。经由ptr可以存取到virtual table。基类的虚函数在派生类虚表中的位置保持不变,最终调用的是,派生类重写的虚函数。执行期所知道的是,所调用的函数的真实实体。
(二)多重继承
有多少个基类,派生类就会有多少个虚表以及指向虚表的虚指针。
基类的不同,被处理的虚表指针不同。(书中说,根据虚表的独一无二的名字,不同的基类,处理不同的表格。难道不是通过虚指针访问虚表的嘛?)。以下三种情况需要调整指针:
- 派生类指针指向派生类对象,使用虚表1,访问虚表2的函数时。
- base2基类的指针指向派生类对象,调用析构函数。
- 派生类对象虚函数执行的返回值是派生类指针,赋给base2基类指针。
不同编译器对该处的处理方法不同,例如,Sun编译器将virtual tables连锁为一个,指向次要虚表的指针,由指向主要虚表的指针加上一个offset。
(三)虚继承下的 Virtual Functions
用单一虚继承来分析
class Point2d
{
public:
Point2d();
virtual ~Point2d();
virtual void mumble();
virtual void z();
private:
int x,y;
}
class Point3d:virtual public Point2d
{
public:
Point3d();
~Point3d();
void z();
private:
int z;
}
// test虚基类指针
code: cout << sizeof(Point2d) << "\t" << sizeof(Point3d) << endl;
outPut: 12 20
// test派生类新增虚函数
class Point3d:virtual public Point2d
{
public:
Point3d();
~Point3d();
void z();
virtual void fun();
private:
int z;
}
code: cout << sizeof(Point3d) << endl;
output: 24
在上一小节分析:单一继承,一个类只有一个虚表。
单一虚继承,在原来的虚函数表的基础上会增加一个虚基类表(暂且这么叫)和指向虚基类表的指针,包含虚基类的offset。
??? 问题:
当派生类新增了虚函数,派生类的大小会增加一个指针的大小。
4.4 指向Member Functions的指针
取一个nonstatic data member的地址,得到的时该member在class布局的offset+1;取一个nonstatic member function的地址,如果是nonvirtual,则得到它在内存中的真正地址;对一个virtual member function取其地址,所获得的是一个索引值。
double (Point2d::*ptr_fun)() = &Point2d::mumble;
// 绑定object调用
(origin.*ptr_fun)();
或
(ptr->*ptr_fun)();
// 一、mumble是nonvirtual function
内化为:(*ptr_fun)(ptr);
// 二、mumble是virtual function,非指针对象调用虚函数时,会压制虚机制。
内化为:(*ptr->vptr[(int)ptr_fun])(ptr);
指向member function的指针声明语法,以及指向"member selection运算符"的指针,其作用就是作为this指针的空间保留者。这也就是为什么static member functions(没有this指针)的类型是“函数指针”,而不是指向member function指针的原因。
(一)支持指向"Virtual member Functions"之指针
对一个virtual member function取其地址,所获得的是一个索引值。上述调用会被内化为一个编译期的式子:
(*ptr->vptr[(int)ptr_fun])(ptr);
编译器必须能够区分:ptr_fun代表内存地址还是Virtual table中的索引值。