非静态成员函数
C++设计准则是非静态成员函数至少必须和非成员函数有相同的效率。
float magnitude3d(const Point3d *_this){}
float Point3d::magnitude3d()const {}
实际上,成员函数是要转化为非成员的形式的,经过以下几步:
- 改写函数签名,安插一个额外的参数到成员函数中,该参数被称为 this指针
- 将每一个对 non-static 数据成员的读写操作,改为经过 this指针的操作
- 将成员函数重新写成一个外部函数。将函数名称经过
magnling
处理,成为程序中唯一的语汇
名称的特殊处理
一般编译器会在成员前后加上类名,形成唯一的命名:
class Bar{
public:
int ival;
};
ival
可能变成ival_3Bar
。
虚成员函数
如果nomalize()
是一个虚成员函数,那么:
ptr->normalize();
//变成如下操作
(*ptr->vptr[1])(ptr);
其中:
- vptr 是编译器产生的指向 vtbl 的指针,它存在于每个声明有或继承有虚函数的类对象中。
- 1 是 vtbl 的中的索引值,关联到
nomalize()
函数 - 第二个 ptr 表示 this指针
static 成员函数
静态成员函数:
- 不能直接存取其类中的非静态成员;
- 不能被声明为 const 、volatile 或 virtual ;
- 不需要经过类对象就可以调用!
如果取一个 static 成员函数的地址,获得的是它在内存中的位置。由于 static 成员函数没有this
指针,所以其地址不是一个“指向成员函数的指针”,而是一个非成员函数指针:
//该函数是一个 static 成员函数
&Point::object_count();
//会得到一个数值,类型是:
unsigned int(*)();
//而不是:
unsigned int(Point3d::*)();
一般用一个静态成员函数返回静态数据成员,比较合理。
4.2 虚成员函数
为了支持虚函数机制,必须能够判断多态对象的运行期类型。
C++中,多态表示以一个public基类的指针或引用,寻址出一个派生类对象的意思。
只要一个类有一个虚函数,就需要运行期信息 ptr->z();
:
- ptr 所指对象的真实类型,使我们选择正确的
z()
实例 - 函数
z()
实例的位置,以便能够调用它
所以需要在多态类对象上增加两个信息:
- 一个字符串或数字,表示 class 的类型
type_info
; - 一个指向某表格的指针,表格中有程序虚函数的地址。
虚函数地址构建方式:虚函数是编译器可知的,且地址是固定的,运行期不能新增或替换。所以虚函数表vtbl 是编译期就可以确定的。每一个 class 都有一个虚函数表,所有虚函数在其中都有一个索引值。在虚函数表的最开始(第一个偏移量处)有 class 的 type_info 。
一个 class 只有一个虚函数表 vtbl ,每个表内含类对象所有虚函数实例的地址,这里的虚函数包括:
- 这个类自己定义的虚函数;
- 这个类重写基类的虚函数;
- 继承自基类的虚函数实例,在派生类中原封不动。
此时发生调用:ptr->z()
,可以在编译器设定虚函数调用:
- 不知道 ptr 的真实类型,但是通过 ptr 可以存储该对象的 vtbl ;
- 虽然不知道哪一个
z()
实例会被调用,但是知道每一个z()
函数地址都被放在 slot4 中
上述信息可以令调用转化为:
(*ptr->vptr[4])(ptr);
接下来就需要运行期的信息才能获得具体的类型信息了。
多重继承下的虚函数
vptr 是每个带有虚函数的类对象自动生成的,是有实体、有大小、存在的,用于指向 vtbl ,寻找到虚表。this指针
是实际不存在的,在类对象被声明时指向类对象的首地址处,在访问函数时,通过 this 的偏移。
因此要审慎地对待this地址调整。
所以如果遇到多重继承,第二个及后继 base class 指针所绑定的派生类对象,需要编译器调整 this指针,否则不能正确偏移,就访问不到函数了。
在多重继承中,一个派生类含有n-1个额外的虚函数表,n表示上一层的基类个数;针对每一个虚函数表,派生类对象都有一个vptr
指向它们。
对于本例而言,有两个 vtbl 会产生:
- 一个主要实体,与Base1(最左端base class)共享
- 一个次要实体,与Base2(第二个base class)有关
当将一个 Derived 对象地址指定给一个 Base1 或者 Derived 指针时,被处理的 vtbl 是主要表格;当指定给一个 Base2 指针时,被处理的 vtbl 是次要表格。
通过指向第二个 base class 的指针调用派生类的虚函数:
Base2 *ptr = new Derived;
//ptr必须向后调整sizeof(Base1)个bytes,到Derived对象起始位置
delete ptr;
通过派生类指针调用派生自第二个基类的虚函数
Derived *pder = new Derived;
//pder必须向前调整sizeof(Base1)个bytes,到Base2起始位置
pder->mumble();