《深度探索C++对象模型》chapter4:Function语意学

本文详细介绍了C++中非静态成员函数、虚拟成员函数和静态成员函数的调用方式。重点讲解了虚拟成员函数的实现原理,包括vtable和vptr的作用。同时探讨了指向成员函数的指针以及内联函数的优化策略。内容涵盖了C++的多态性和执行期类型判断法,揭示了C++如何通过编译器内部转换实现高效的成员函数调用。
摘要由CSDN通过智能技术生成

1 Member的各种调用方式

1.1 非静态成员函数

C++的设计准则之一:nonstatic member function至少必须和一般的nonmember function有相同的效率。

//Point3d::magnitude()
float Point3d::magnitude() const {
    return sqrt(_x * _x + _y * _y + _z * _z);
}
//magnitude()的nonmember定义
float magnitude3d(const Point3d * this) {
    return sqrt(this->_x * this->_x + 
                this->_y * this->_y + 
                this->_z * this->_z);
}

编译器内部对待member函数实例会将其转换为对等的nonmember函数实例,转化步骤如下:

  1. 改写函数原型以安插额外参数this指针到 member function中,用以提供一个存取通道,使类对象得以将此函数调用

  2. 将每一个对非静态数据成员的存取操作改为经由this指针来存取

  3. 将成员函数重写成一个外部函数,将函数名称经过“mangling”处理,使其独一无二

名称的特殊处理name mangling:

  1. member名称前面会被加上class名称,形成独一无二的命名

  2. 对于重载的成员函数还可参考函数原型将其参数类型编码进名称。

将参数和函数名称编码在一起,可使编译器在不同的被编译模块之间达成一种有限的类型检验

1.2 虚拟成员函数

Point3d obj;
Point3d *ptr = &obj;
//若normalize()是一个virtual member function,那么以下的调用:
ptr->normalize();
//将会被内部转化为:
(*ptr->vptr[1])(ptr);

其中:

  1. vptr表明由编译器产生的指针,指向virtual table。它被安插在每一个“声明有(或继承自)一个或多个virtual functions"的类对象中
  2. 1 是vtable slot的索引值,关联到normalize()
  3. 第二个ptr表示this指针
//如果magnitude也是一个虚函数,则它在normalize()中的调用操作将被转换为:
//register float mag = magnitude();
register float mag = (*this->vptr[2])(this);

由于Point3d::magnitude()是在Point3d::normalize()中被调用的,而后者已经由虚拟机制而决议妥当,所以显式地调用"Point3d实例"会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作

register float mag = Point3d::magnitude();

如果magnitude()声明为inline函数,会更有效率。使用域操作符(::)显式调用一个虚函数,其决议方式会和非静态成员函数一样:

register float mag = magnitude——7Point3dFv(this);

对于经由不支持多态的对象的调用,即经由一个类对象调用一个虚函数,其操作应该总是被编译器像对待一般非静态成员函数一样进行决议:

//Point3d obj;
obj.normalize();
//(*obj.vptr[1])(&obj); 语意正确,但无必要
normalize_7Point3dFv(&obj);

1.3 静态成员函数

静态成员函数的调用操作会被转换为一般的nonmember函数调用

//如果Point3d::normalize()是一个static member function
//obj.normalize()
normalize__7Point3dSFv();
//ptr->normalize()
normalize__7Point3dSFv();

引入静态成员函数的原因:存取静态数据成员

Static member function的主要特性:没有this指针

次要特性:

  1. 不能够存取类中的非静态成员
  2. 不能够声明为const,volatile,virtual
  3. 不需要经由类对象才被调用

如果取一个静态成员函数的地址,获得的是其在内存中的位置,也就是其地址。由于static member function没有this指针,所以其地址的类型并不是一个“指向class member function的指针”而是一个“nonmember函数指针”

2 虚拟成员函数

virtual function的实现模型:每一个class有一个virtual table,内含该class之中有作用的virtual function地址,然后每个object有一个vptr,指向virtual table所在。

C++中的多态:以一个public base class的指针(或reference),寻址出一个derived class object

“消极多态”:扮演输送机制,可在编译时期完成

“积极多态”:指出的对象被真正使用

为支持虚函数机制,必须首先能够对于多态对象有某种形式的“执行期类型判断法(runtime type resolution)”,也就是在支持某种形式的“执行期多态(runtime polymorphism)”时对class提供额外的执行期信息。

识别class是否支持多态,唯一适当的方法即看它是否有任何virtual function。只要class拥有一个virtual class,它就需要这份额外的执行期信息

额外信息的内容:(若有此调用 ptr->z(),z()为虚函数)

  1. ptr所指对象的真实类型
  2. z()实例的位置

在实现上,在每个多态的类对象身上增加了两个member:

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

如何找到函数地址:

  1. 类对象中插入由编译器内部产生的指针,指向该表格
  2. 为找到函数地址,每个虚函数被指派一个表格索引值

vtable所含的active virtual function的类型:

  1. 重写的基类虚函数实例
  2. 继承自基类的虚函数实例
  3. 纯虚函数实例

2.1 单一继承下的虚函数

在这里插入图片描述

2.2 多重继承下的虚函数

在这里插入图片描述

2.3 虚拟继承下的虚函数

对于虚拟继承而言,不论是基类还是派生类都需要有一个指针来维护自己的虚表

在这里插入图片描述

3 指向成员函数的指针

//声明指向成员函数的指针
double (Point::*pmf)();
//声明并定义
double (Point::*coord)() = &Point::x;
coord = &Point::y;

非静态成员函数:若该函数非虚函数,则取其地址可得到它在内存中的真正地址

虚函数:对virtual member function取地址,所能获得的是在vtable中的索引值

编译器会根据不同的class特性提供多种指向成员函数的指针形式

4 内联函数

  1. 在类内定义的函数,默认内联
  2. 关键词inline只是一项请求,编译器会根据其执行成本作合理的扩展
  3. 对于形参的处理并非简单的用实参去替代,而是会根据其性质作合理操作
  4. 局部变量也不能直接复制,而是要进行mangling操作,防止和程序中的变量重名。
  5. 局部变量和副作用的参数会导致大量临时对象的产生
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值