非静态成员函数

C++设计准则是非静态成员函数至少必须和非成员函数有相同的效率。

  1. float magnitude3d(const Point3d *_this){}
  2. float Point3d::magnitude3d()const {}

实际上,成员函数是要转化为非成员的形式的,经过以下几步:

  1. 改写函数签名,安插一个额外的参数到成员函数中,该参数被称为 this指针image.png
  2. 将每一个对 non-static 数据成员的读写操作,改为经过 this指针的操作
  3. 将成员函数重新写成一个外部函数。将函数名称经过magnling处理,成为程序中唯一的语汇

image.png
名称的特殊处理

一般编译器会在成员前后加上类名,形成唯一的命名:

  1. class Bar{
  2. public:
  3. int ival;
  4. };

ival可能变成ival_3Bar

虚成员函数

如果nomalize()是一个虚成员函数,那么:

  1. ptr->normalize();
  2. //变成如下操作
  3. (*ptr->vptr[1])(ptr);

其中:

  • vptr 是编译器产生的指向 vtbl 的指针,它存在于每个声明有或继承有虚函数的类对象中。
  • 1 是 vtbl 的中的索引值,关联到nomalize()函数
  • 第二个 ptr 表示 this指针

static 成员函数

静态成员函数:

  • 不能直接存取其类中的非静态成员
  • 不能被声明为 const 、volatile 或 virtual ;
  • 不需要经过类对象就可以调用!

如果取一个 static 成员函数的地址,获得的是它在内存中的位置。由于 static 成员函数没有this指针,所以其地址不是一个“指向成员函数的指针”,而是一个非成员函数指针

  1. //该函数是一个 static 成员函数
  2. &Point::object_count();
  3. //会得到一个数值,类型是:
  4. unsigned int(*)();
  5. //而不是:
  6. unsigned int(Point3d::*)();

一般用一个静态成员函数返回静态数据成员,比较合理。

4.2 虚成员函数

为了支持虚函数机制,必须能够判断多态对象的运行期类型
C++中,多态表示以一个public基类的指针或引用,寻址出一个派生类对象的意思。

只要一个类有一个虚函数,就需要运行期信息 ptr->z();

  • ptr 所指对象的真实类型,使我们选择正确的 z() 实例
  • 函数z()实例的位置,以便能够调用它

所以需要在多态类对象上增加两个信息:

  1. 一个字符串或数字,表示 class 的类型type_info
  2. 一个指向某表格的指针,表格中有程序虚函数的地址。

虚函数地址构建方式:虚函数是编译器可知的,且地址是固定的,运行期不能新增或替换。所以虚函数表vtbl 是编译期就可以确定的。每一个 class 都有一个虚函数表,所有虚函数在其中都有一个索引值。在虚函数表的最开始(第一个偏移量处)有 class 的 type_info 。

一个 class 只有一个虚函数表 vtbl ,每个表内含类对象所有虚函数实例的地址,这里的虚函数包括:

  • 这个类自己定义的虚函数;
  • 这个类重写基类的虚函数;
  • 继承自基类的虚函数实例,在派生类中原封不动。

image.png
此时发生调用:ptr->z(),可以在编译器设定虚函数调用:

  • 不知道 ptr 的真实类型,但是通过 ptr 可以存储该对象的 vtbl ;
  • 虽然不知道哪一个z()实例会被调用,但是知道每一个z()函数地址都被放在 slot4 中

上述信息可以令调用转化为:

  1. (*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 是次要表格
image.png

  • 通过指向第二个 base class 的指针调用派生类的虚函数:

    1. Base2 *ptr = new Derived;
    2. //ptr必须向后调整sizeof(Base1)个bytes,到Derived对象起始位置
    3. delete ptr;
  • 通过派生类指针调用派生自第二个基类的虚函数

    1. Derived *pder = new Derived;
    2. //pder必须向前调整sizeof(Base1)个bytes,到Base2起始位置
    3. pder->mumble();