文章目录

  • 索引
  • 一、简介
    • 1、成员的各种调用方式
      • 1.1 nonstatic member functions
      • 1.2 virtual member functions
      • 1.3 static member function
      • 1.4 virtual member functions
      • 1.5 多重继承下的virtual functions
      • 1.6 虚拟继承下的virtual functions
    • 2、函数的效能
    • 3、指向成员函数的指针
    • 4、支持指向virtual member functions的指针
    • 5、多重继承下,指向成员函数的指针
    • 6、内联函数

索引

C++【对象模型】| 【01】简单了解C++对象模型的布局
C++【对象模型】|【02】构造函数何时才会被编译器自动生成?
C++【对象模型】|【03】拷贝构造是如何工作的,何时才会用到呢?
C++【对象模型】 | 【04】程序在内部被编译器如何转化?
C++【对象模型】 | 【05】类与类之间各种关系下对数据成员的存取、绑定、布局
C++【对象模型】| 【06】类中各种函数的刨析
C++【对象模型】| 【07】构造、析构、拷贝做了哪些事?
C++【对象模型】| 【08】类在执行期会处理哪些事呢?
C++【对象模型】| 【09】类模板、异常处理及执行期类型识别

一、简介

成员函数分类:静态函数、非静态函数、虚函数;
静态函数:不能为声明为const、没有this指针,不能直接存取非静态成员数据;
非静态函数:内含一个this指针;
虚函数:通过虚vptr,地址存储在虚表中;
class Point3d  {public:Point3d normalize() const {register float mag = magnitude();Point3d normal;normal._x = _x/mag;normal._y = _y/mag;normal._z = _z/mag;return normal;}float magnitude() const {return sqrt(_x*_x + _y*_y + _z*_z);}
private:float _x, _y, _z;
};

1、成员的各种调用方式

1.1 nonstatic member functions

该类型函数和非成员函数具有相同的效率;
float func(const Point3d *_this) {} == float Point3d::func() const {}
实际上成员函数被内化为非成员函数;

成员函数如何转换为非成员函数

- 改写函数原型,安插一个this指针,用于存取数据成员;
- 将成员函数该为一个外部函数,将该名称经由`mangling`处理,让其独一无二;例:转换
【成员函数】
Point3d Point3d::func(){return sqrt(_x * _x +_y * _y + _z * _z);
}
【非成员函数】
Point3d Point3d::func(Point3d *const this){return sqrt(this->_x * this->_x + this->_y * this->_y + this->_z * this->_z);
}
【mangling处理】
extern func_7Point3dFv(register Point3d *const this);
当调用该函数时:
非指针调用,obj.func(); ==> func_7Point3dFv(&obj);

mangling

对于成员函数,为了区分一般会加上当前类的名称,避免派生类中出现重名函数;
为了放置函数重载的同名,为此加上了参数的类型和参数个数,来加以区分;如果禁止使用该方法对函数进行别名,那么请使用extern"C";

1.2 virtual member functions

当调用一个虚函数时会通过vptr索引到虚表中的地址从而进行函数调用;
如:ptr->normalize() ==> (*ptr->vptr[1])(ptr);  【如果上述代码中normalize为虚函数】
其中ptr为this指针;1为func在虚表中的索引值;vptr为虚表;

显示调用会抑制虚拟机制,从而不会产生不必要的重复调用操作

假设上述的magnitude也为虚函数,而它在normalize函数中,该函数已将虚拟机制决议妥当,故可直接obj::magnitude调用,效率会更高,减少通过虚表
索引等不必要的操作;

1.3 static member function

当有非静态数据成员在成员函数中被存取时,才需要this指针;那么当一个类中不需要对类中的成员进行存取,那么久没有必要通过一个obj来调用;
- 不能直接存取class中的非静态数据成员;object_count((Point3d*)0) =>0强转为指针,提供this指针
- 不能被声明为const、volatile、virtual;
- 不需要经由class obj才被调用;【内存地址】
由于静态成员函数不在类中,那么对其取地址,则得到将是在内存中的位置;
即unsigned int(*)()  而不是usigned int(Point3d::*)();【好处】
静态成员函数可以成为一个回调函数,应用于线程上;

1.4 virtual member functions

C++【对象模型】| 虚函数表 & 多态如何调用虚函数

C++为了支持虚函数机制,必须先要对多态对象有某种形式的执行期类型判断法(需要在执行期的相关信息);
【相关信息】
- 所参考到的对象地址;
- 对象类型的某种编码或结构的地址;

此类信息将存储于何处呢?

考虑将其放置在对象本身;
- 当面对class的声明时需要存储,这将保留对struct的兼容性;
- 当class支持执行期多态时需要存储;
- 而对于一个类是否支持多态,则查看其是否有任何虚函数,即当有一个虚函数即需要该信息;

什么样的信息需要存储呢?

当调用一个虚函数时,我们需要知道该对象的真实类型以及该函数的位置;
为此我们在多态的类对象上增加两个成员:
- 一个字符串或数字,用来表示class的类型;
- 一个指针,指向某个表格,记录虚函数的执行期地址;【下一步我们将构建该表格】:
- 由于虚函数在编译起即获知,且在程序执行表格大小和内存不会改变;
【该如何找到函数地址】:
- 通过指针指向该表格进行查找;
- 通过表格索引值,找到函数地址;
【执行期该做什么】
- 只需在特定的虚表中激活相应的虚函数;

1.5 多重继承下的virtual functions

class Base1 {public:Base1();virtual ~Base1();virtual void speakClearly();virtual Base1 *clone() const;protected:float data_base1;
};class Base2 {public:Base2();virtual ~Base2();virtual void mumble();virtual Base2 *clone() const;protected:float data_Base2;
};class Derived : public Base1, public Base2 {public:Derived();virtual ~Derived();virtual Derived *clone() const;protected:float data_Derived;
};
当创建一个对象时:Base2 *pbase2 = new Derived;
编译器转换 =====> Derived *temp = new Derived; Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;
this调用虚函数需要在执行期完成,因为执行期才能确定好offset;,而如何才能知道offset呢?

offset的大小需要加入this上头的代码,编译器该在何出插入?

起初采用将虚表扩大,让此处容纳所需的this指针,此时表中每一个slot不在是一个指针,而是一个集合体,内含offset以及函数地址;
于是:(*pbase2->vptr[1](pbase2)) ⇒ (*pbase2->bptr[1].faddr)(pbase2 + pbase2->vptr[1].offset);
其中faddr存储虚函数地址,offset内含this偏移值;该做法不管该虚函数是否要被调用,都需要扩张其大小;

更加有效的做法thunk

thunk用处:
- 以适当的offset调整this指针;
- 跳到virtual function;该技术允许virtual table solt继续内含一个简单的指针,因此不需要其他额外的负担;
即slot中地址可以直接指向虚函数,当它需要调整this指针时可以指向一个相关的thunk;

调用this指针的第二个负担

调用的可以有两种:将会导致同一函数在虚表中可能需要多个对应的slot
- 由子类或第一个基类(base1)调用;
- 由第二个基类(base2)调用;如:
Base1 *pbase1 = new Derived;
Base2 *pbase2 = new Derived;
delete pbase1;
delete pbase2;
上述两个delete中调用相同的Derived destructor需要两个不同给的virtual table slots;
- pbase1不需要调整this,由于它在Derived起始处;
- pbase2需要调整this,其virtual table slot需要相关的thunk地址

主次实例

当一个子类内含n-1个虚表时,n为上一层基类的个数,此时子类将会有两个虚表产生:
- 主要实例,与Base1共享;
- 次要实例,与base2有关;

如何调节执行期链接器的效率

将多个virtual tables连锁成一个,形成次要表格,若要获取可通过主要表格名称加上一个offset即可获得;

第二个base class 会影响对虚函数的支持有三种情况

【通过一个指向第二个base class的指针,调用子类虚函数】
当调用时,该指针必须调整以指向子类的起始处;
【一个指向子类的指针,调用第二个基类中一个继承而来的虚函数】
子类指针必须再次调整,以指向第二个基类子对象;
【允许一个虚函数的返回值有所变化】:
当我们指向第二个基类的指针来调用clone时,该指针也需要被调整,否则返回一个指向子类的对象;

1.6 虚拟继承下的virtual functions

class Point2d {public:Point2d(float = 0.0, float = 0.0);virtual ~Point2d();virtual void mumble();virtual float z();protected:float _z, _y;
};class Point3d : public virtual Point2d {public:Point3d(float = 0.0, float = 0.0, float = 0.0);~Point3d();float z();protected:float _z;
};
如上述继承关系,虚基类从另一个虚基类派生,都支持虚函数和非静态数据成员,两个都会导致类内膨胀,且需要offset及各种调
整;
为此,建议不要再一个虚基类中声明非静态数据成员;

2、函数的效能

经过测试非成员函数、静态成员函数、非静态成员函数都被转换为完全相同形式,效率相同;
而内联函数能够达到最高的效率可以节省一般函数调用所带来的额外负担;虚函数再继承中,没多一层继承其执行时间将会明显增加,由于没增加一层久会多增加一个额外的vptr设定;

3、指向成员函数的指针

当取一个非静态成员函数时,它需要绑定再类上才能够调用该函数,故需要一个this指针;
double(Point::* pmf)() = &Point::x;    // 初始化成员函数指针 (或pmf=&Ponit::x)
(origin.*pmf)(); // 调用函数(或(ptr->*pmf)())
再由编译器转化==>(pmf)(&origin);

4、支持指向virtual member functions的指针

float (Point::*pmf)() == &Point::z;    // 将一个虚函数z赋给pmf
Point *ptr = new Point3d;
ptr->z();        // 调用虚函数
ptr->*pmf();
上述ptr调用pmf仍是调用z()?如果是,那虚拟机制是如何实现该情况的?
pmf接受一个成员函数或一个虚成员函数都能正确调用时由于内部能够持有两种数值:
- 内存地址或 虚表的索引值;
通过(((int)pmf) & ~127) ? (*pmf)(ptr) : (*ptr->vptr[(int)pmf](ptr));       // 前为非虚函数,后为虚函数
当该技巧只能满足类总最多只有128个虚函数;

5、多重继承下,指向成员函数的指针

// 该结构用以支持多重继承下成员函数的指针
struct __mptr {int delta;   // 虚函数地址int index;  // 虚表索引,默认为-1union {ptrtofunc faddr; int v_offset;};
};
该结构具有以下缺点:
- 每次调用都需要检查是否由虚函数;
- 当传递一个不变值的指针给成员函数时,它需要产生一个临时性对象;
Microsoft对于继承提出多种处理方式:
- 一个单一继承实例,持有vcall thunk或函数地址;
- 一个多重继承实例,持有faddr和delta两个成员;
- 一个虚拟继承实例,持有4个成员;

6、内联函数

处理一个内联函数由两个阶段:
- 分析函数定义,来决定函数的本质内联能力,是否能成员内联函数;
- 真正的内联函数会扩展操作,当调用时;会给参数的求值操作以及临时性对象的管理;

形式参数

当内联扩展期间,形参会被实参取代;当面对由副作用的参数,将会引入临时对象;

局部变量

如果内联函数以单一表达式扩展多次,则每次扩展都需要自己的一组局部变量;
若以分离的多个式子被扩展多次,则只需要一组局部变量;

C++【对象模型】| 【06】类中各种函数的刨析相关推荐

  1. 类中成员函数声明后面的const的含义

    这个const一般是对类中成员函数属性的声明,但这个声明怪怪的,只能放在函数声明的尾部,大概是因为其它地方都已经被占用了.这个声明表示这个函数不会修改类中的任何数据成员.如果在编写const成员函数时 ...

  2. C++深度解析 类中的函数重载 -- 全局函数,普通成员函数,静态成员函数(28)

    C++深度解析 类中的函数重载 -- 全局函数,普通成员函数,静态成员函数(28) 函数重载的回顾 函数重载的本质为相互独立的不同函数 C++中通过函数名和函数参数确定函数调用 无法直接通过函数名得到 ...

  3. Python的类中定义函数的三种方式及其利用

    类中方法/函数: 普通方法 直接定义(含self) 类方法 @classmethod 静态方法 @staticmethod 类中变量: 静态变量 普通变量 定义在构造函数中(__init__) 看代码 ...

  4. 【Python——类】 同一个类中一个函数里调用另一个函数的方法

    [Python-类] 同一个类中一个函数里调用另一个函数的方法 class Solution:def a(self):self.b() # 注意这种写法:self.类名def b(self):prin ...

  5. 类中const函数及非const函数的调用规则

    转自:点击打开链接 类中const函数及非const函数的调用规则 class Student { public:int getAge(){return m_age;}int getAge() con ...

  6. 类中成员函数的函数指针定义以及使用

    在algorithm算法中经常会碰到传递函数指针的情况,在这里面他们把这种类型叫做谓词,当然lambda表达式就是 谓词的一种.这次内容要说的当类的成员函数做为谓词时,是什么样子的,如何使用呢? 类中 ...

  7. FreeRtos学习笔记(11)查找就绪任务中优先级最高任务原理刨析

    FreeRtos学习笔记(11)查找就绪任务中优先级最高任务原理刨析 怎么查找就绪任务中优先级最高的? tasks.c中声明了一个全局变量 uxTopReadyPriority,任务从其他状态进入就绪 ...

  8. C++对象模型7——类的成员函数、反汇编虚析构函数、RTTI、多态的开销

    一.类成员函数 class test { public:void myfunc(){}virtual void vfunc() {}static void sfunc() {} };void myfu ...

  9. Python class 类中 __init__ 函数

    什么是类? 类 是通过执行类语句创建的特定对象类型.类对象 被当作模板来创建实例对象,实例对象包含了特定于数据类型的数据(属性)和代码(方法). 类可以基于一个或多个的其他类,称之为基类(ES),它继 ...

最新文章

  1. 成功将BlogEngine 1.5 升级到了BlogEngine 2.0
  2. [hadoop源码阅读][9]-mapreduce-概论
  3. 【数据安全案例】交警计算机系统再遭***,交通违法记录随意删除
  4. php云开发要去的地方
  5. Android内核开发:系统编译输出的镜像文件
  6. c语言已知斜率 求倾角,倾斜解的问题
  7. 二叉树 二度节点和叶子节点之间的数量关系
  8. mysql:Column id cannot be null
  9. 【内网安全】域横向PTHPTKPTT哈希票据传递
  10. 美国确诊超46万!美国州长竟然抢起高龄程序员了,什么情况?
  11. AngularJS内置指令 ng-xxx
  12. access 此程序未正确安装_一款让“微软爸爸杀不死”的数据库软件——生于1992的Access...
  13. ipv6地址_「案例」路由器怎么自动获取IPv6地址?
  14. tensorflow之cast
  15. 安卓玩机之xposed框架安装
  16. 2016年我的学习记录与搜索到的网站,将持续更新,主要介绍我在公司应用ecshop的学习记录...
  17. 台架控制器-AVL_ISAC学习
  18. background简写属性
  19. ios 穿山甲广告联盟集成_GitHub - ducaiwei/Pangolin: Flutter 广告SDK-字节跳动-穿山甲 集成...
  20. png怎么转换成jpg格式?如何转换照片格式为jpg?

热门文章

  1. OneNote同时设置中英文字体显示(雅黑+Consolas)
  2. [SGN]SGN: Sequential Grouping Networks for Instance Segmentation
  3. window.scroll 浏览器滚动条的参数总结..........
  4. java计算机毕业设计河池市旅游信息系统源码+mysql数据库+系统+lw文档+部署
  5. 字符串:找第一个只出现一次的字符(python实现)
  6. 2021年8月国产数据库排行榜:TiDB稳榜首,达梦返前三,Kingbase进十强,各厂商加速布局云生态
  7. 【JavaSE系列】递归经典练习题
  8. 阿里云服务器配置微信公众号token验证失败解决办法
  9. android 沉浸式状态栏字体颜色,改变Android状态栏字体颜色和实现沉浸式状态栏
  10. jplayer - html5视频播放器插件