1、Member的各种调用方式

1.1 Nonstatic Member Functions

  实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤:

  1.给函数添加额外参数——this。

  2.将对每一个nonstaitc data member的存取操作改为this指针来存取。

  3.将member function 重写成一个外部函数。对函数名精选mangling 处理,使之成为独一无二的语汇。

class A{
public:
int x, y;
int func();
} int A::func(){ return x+y; } //Nonstatic Member function
int A::func(A *const this) //1.添加额外参数this指针
{ return this->x + this->y;} //2.改为由this指针存取成员数据 extern int func_1AFv(A *const this) //3.对函数名进行“mangling”修饰 obj.func() ---> func_1AFv(&obj)

1.2 Virtual Member Functions

  将 ptr->f();   //f()为virtual member function 内部转化为 (*ptr->vptr[1])(ptr); 其中:

  vptr表示编译器产生的指针,指向virtual table。它被安插在每一个声明有(或继承自)一个或多个virtual functions 的class object 中。

  1 是virtual table slot的索引值,关联到normalize()函数,此处表示第一个函数。

  第二个ptr表示this指针

//明确的调用可以抑制虚拟机制
A::func()
// 通过该obj调用不支持多态
obj.func() ---> func_1AFv(&obj)

  通过class object调用virtual function的操作会被编译器转换为一般的nonmember member function,不呈现多态。

1.3 Static Member Functions

  • 参数没有this
  • 不能被声明为 const volatile 或virtual。
  • 不需要经过object 调用,可以使用class直接去调用
  • 不能够直接存取其class中的非static member ,在static的作用域范围内,要想访问非static的数据成员,就得使用this指针

  Static Member Function会被编译器转化为nonmember形式,实际上该类函数差不多等同于nonmember function。

  一个static member function 会提出于class声明之外,并给予一个经过mangling的适当名称。如果取一个static member function 的地址,获得的是其在内存的位置也就是地址,而不是一个指向“class member function”的指针,如下: 

  &Point::count();  会得到一个数值,类型是:unsigned int(*)(); 而不是:unsigned int(Point::*)();

static int A::func()   //静态成员函数
//各种方式调用静态函数,均转换成通过类作用于直接调用
ptr->func()/obj.func()/A::func() --> A::func()
//函数mangling修饰
A::func() ---> func_1ASFv() //SFv表示静态成员函数,参数void

2、Virtual Member Funcitons

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

2.1 编译期间完成的事情

  • 给每一个class object安插由编译器内部产生的vptr
  • 给vtbl每一个virtual function一个固定的slot,在整个继承体系中保持与特定virtual funciton相联。这些active virtual function 包括:

    这个class 所定义的函数实体(改写(overriding)一个可能存在的base class virtual function函数实体。

    继承自base class 的函数实体(不被derived class改写)
    一个pure_virtual_calle()

2.2 一个类继承函数virtual table的三种可能

  1.继承base class 所声明的virtual functions的函数实体。正确地说,是该函数实体的地址会被拷贝到derived class的virtual table相对应的slot之中。

  2.使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。

  3.可以加入一个新的virtual function。这时候virtual table 的尺寸会增大一个slot放进这个函数实体地址。

  2.3 单一继承

  下面一个类Point的定义:

class Point {
public:
virtual ~Point(); //slot 1
virtual Point& mult (float) = //slot 2
float x() const {return _x;}
virtual float y() const {return ;} //slot 3
virtual float z() const {return ;} //slot 4
protected:
Point (float x = 0.0);
float _x;
} class Point2d:public Point {
public:
~Point2d(); //slot 1
Point2d& mult (float) //函数覆盖 slot 2
float y() const {return _y;} //函数覆盖 slot 3
protected:
float _y;
} class Point3d:public Point2d {
public:
~Point3d(); //slot 1
Point3d& mult (float) //函数覆盖 slot 2
float z() const {return _z;} //函数覆盖 slot 4
protected:
float _z;
} //一般来说我们事先并不知道ptr所指对象真正类型
//所以不知道哪个mult实体会被调用,但mult()固定在slot 2中是确定的
ptr->mult()
//因此编译器如下转换
//执行期唯一需要做的就是确定哪一个slot 2的实体
(*ptr->vptr[])(ptr)

  Point2d继承自Point。Point3d继承自Point2d。那么内存模型如图:

  

  编译时期设定virtual function的调用:

  一般而言,我并不知道ptr 所指对象的真正类型。然而可以经由ptr 可以存取到该对象的virtual table。

  虽然我不知道哪个Z()函数实体被调用,但知道每一个Z()函数地址都被放置slot 4的索引。

  这样我们就可以将ptr->z();转化为:(*ptr->vptr[4])(ptr);

  唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体

2.4 多重继承下的 Virtual Functions

  多重继承下,通常派生类会有多个virtual table ,最左边基类的称之为:“主要表格”,第二或更过多基类的表格称为:“次要表格”(参考上图),派生类的主要表格和次要表格可以连在一起,比如Sun的编译器的策略就是这样的。

class Base1{                                   class Base2{
public: public:
Base1(); Base2();
virtual ~Base1(); virtual ~Base2();
virtual void SpeakClearly(); virtual void mumble();
virtual Base1* clone() const; virtual Base2* clone()const;
}; };

  这两个类我故意并列在一起,Base1和Base2的区别就是两个不同的虚函数void SpeakClearly()void mumble();

    class Derived: public Base1,public Base2{
public:
  Derived();
virtual ~Derived();
virtual Derived* clone()const;
protected:
float data_derived;
};

  那么这几个类的virtual table的布局如下:

  

  

  

  多重继承下:derived类会分别重写“主要表格”和“次要表格”

2.5 虚拟继承下的虚函数

  

3、函数的效能

  nonmemeber、static member或nonstatic member函数都被转换为完全相同形式,所以三者效率完全相同。

4、指向Member Function的指针

4.1 nonstatic data member

  取一个nonstatic data member的地址,得到的结果是该member在class 布局中的bytes位置,所以它需要绑定于某个class object的地址上,才能够被存取。

4.2 nonvirtual function指针

  得到真实地址,但它需要绑定与某个class object上,才能通过它调用。

double (Point::*coord) = &Point::x;  //nonvirtual function
//调用函数
(obj.*coord) ()
//被编译器转化
(coord)(&obj) //&obj为this指针

  static member function函数地址为普通函数指针,因为没有this指针。

4.3 virtual function指针

  virtual function地址在编译时期是未知的,但是与之相关的vtbl slot的索引值是固定的。所以取virtual function地址只能获得其索引值。

class Point{
public:
virtual ~Point(); //slot 1
virtual float z(); //slot 2
} float (Point::*pmf) = Point::z; //= 2
Point *ptr = new Point2d;
ptr->*pmf;
//被编译器转化为
(*ptr->vptr[(int)pmf](ptr));

4.4 多重继承下 member function指针

  问题:如何区别Member Function指针指向地址还是virtual table索引? 
  方法:修改Member Function指针mptr结构体,mptr->index正负判别。

struct __mptr {
int delta;
int index; //virtual function时:vtbl索引 nonvirtual时为-1
union{
ptrtofunc faddr; //nonvirtual 时函数地址
int v_offse;
}
}

5、Inline Funcitons

  inline函数参数带有副作用,或者是以一个单一的表达式做多重调用,或是inline函数中有多个局部变量,都会产生临时对象,可能会产生大量的扩展码,是程序的大小膨胀,所以inline函数的使用必须要谨慎。

5.1 对于单一表达式的多重调用

  对于如下incline函数:

    inline Point operator+ (const Point& lhs, const Point& rhs)
{
Point new_pt;
new_pt.x(lhs.x() + rhs.x());//x()函数为成员变量_x的get,set函数
return new_pt;
}

  由于该函数只是一个表达式,在cfront中,则其第二或者后继的调用操作不会被扩展,会变成:

 new_pt.x = lhs._x + x__5PointFV(&rhs); 

  没有带来效率上的提升,需要重写为:

    new_pt.x(lhs._x + rhs._x); 

5.2 inline函数对于形式(Formal)参数的处理

  有如下inline函数:

    inline int min(int i, int j)
{
return i < j ? i : j;
}

  如下三个调用:

    inline int bar()
{
int minVal;
int val1 = ;
int val2 = ; minVal = min(val1, val2);//case 1
minVal = min(, );//case 2
minVal = min(foo(), bar() + );//case 3 return minVal;
}

  对于case1,调用会直接展开:

    minVal = val1 < val2 ? val1 : val2;

  case2直接使用常量:

    minVal = ;

  对于case3,由于引发了参数的副作用,需要导入临时对象,以避免重复求值(注意下面逗号表达式的使用):

    int t1;
int t2; minVal = (t1 = foo()), (t2 = bar() + ), t1 < t2 ? t1: t2;

5.3 inline函数中引入了局部变量

  改写上述min函数,引入一个局部变量:

    inline int min(int i, int j)
{
int minVal = i < j ? i : j;
return minVal;
}

  对于如下调用:

    int minVal;
int val1 = ;
int val2 = ;
minVal = min(val1, val2);

  为了维护局部变量,会被扩展为:

    int minVal;
int val1 = ;
int val2 = ; int __min_lv_minVal;//将inline函数局部变量mangling
minVal = (__min_lv_minVal = val1 < val2 ? val1: val2), __min_lv_minVal;

  再复杂一些的情况,例如局部变量加上有副作用的参数,会导致大量临时性对象的产生:

    minVal = min(val1, val2) + min(foo(), bar() + ); 

  会被扩展成为,注意逗号表达式,由左至右计算各个分式,以最右端的分式值作为最终值传回:

    int __min_lv_minVal_00;
int __min_lv_minVal_01; int t1;
int t2; minVal = (__min_lv_minVal_00 = val1 < val2 ? val1 : val2, __min_lv_minVal_00) +
(__min_lv_minVal_01 = (t1 = foo(), t2 = bar() + , t1 < t2 ? t1 : t2), __min_lv_minVal_01);

  会产生多个临时变量,所以inline函数的使用必须要谨慎。

【C++对象模型】第四章 Function 语意学的更多相关文章

  1. 第4章 Function语意学

    第4章 Function语意学 目录 第4章 Function语意学 4.1 Member的各种调用方式 Nonstatic Member Function(非静态成员函数) virtual Memb ...

  2. 《深度探索C++对象模型》第二章 | 构造函数语意学

    默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...

  3. 【C++对象模型】第二章 构造函数语意学

    1.Default Constructor 当编译器需要的时候,default constructor会被合成出来,只执行编译器所需要的任务(将members适当初始化). 1.1  带有 Defau ...

  4. 深度探索C++对象模型第四章:函数语义学

    C++有三种类型的成员函数:static/nonstatic/virtual 一.成员的各种调用方式 C with class 只支持非静态成员函数(Nonstatic member function ...

  5. 第四章、前端之BOM和DOM

    目录 第四章.前端之BOM和DOM 一.解释BOM和DOM 二.window对象 三.window子对象 四.弹出框 五.计时相关 六.HTML的DOM树 七.查找元素 八.节点操作 九.JS操作CS ...

  6. 05 技术内幕 T-SQL 查询读书笔记(第四章)

    第四章 子查询:在外部查询内嵌套的内部查询(按照期望值的数量分为,标量子查询 scalar subqueries,多值子查询multivalued subqueries)(按照子查询对外部查询的依赖性 ...

  7. 读《编写可维护的JavaScript》第四章总结

    第四章 变量 函数和运算符 4.1 ① 变量声明 变量声明是通过var语句来完成的,并且所有的var语句都提前到包含这段逻辑的函数的顶部执行. function doSomething() { + v ...

  8. KnockoutJS 3.X API 第四章 表单绑定(11) options绑定

    目的 options绑定主要用于下拉列表中(即<select>元素)或多选列表(例如,<select size='6'>).此绑定不能与除<select>元素之外的 ...

  9. KnockoutJS 3.X API 第四章(13) template绑定

    目的 template绑定(模板绑定)使用渲染模板的结果填充关联的DOM元素. 模板是一种简单方便的方式来构建复杂的UI结构 . 下面介绍两种使用模板绑定的方法: 本地模板是支持foreach,if, ...

随机推荐

  1. 关于docker 基础使用记录

    Docker Hub地址:https://hub.docker.com Docker Hub 存放着 Docker 及其组件的所有资源.Docker Hub 可以帮助你与同事之间协作,并获得功能完整的 ...

  2. Alpha 冲刺(1/10)

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作,对多个目标检测及文字识别模型进行评估.实验,选取较 ...

  3. Alpha冲刺——第二天

    Alpha第二天 听说 031502543 周龙荣(队长) 031502615 李家鹏 031502632 伍晨薇 031502637 张柽 031502639 郑秦 1.前言 任务分配是VV.ZQ. ...

  4. Response.End方法

    文章:在try...catch语句中执行Response.End()后如何停止执行catch语句中的内容 调用Response.End()方法能保证,只输出End方法之前的内容. 调用Context. ...

  5. spring框架(1)— 依赖注入

    依赖注入 spring核心容器就是一个超级大工厂,所以的对象(数据源.hibernate SessionFactory等基础性资源)都会被当做spring核心容器的管理对象——spring把容器中的一 ...

  6. P1291 [SHOI2002]百事世界杯之旅

    题目描述 “……在2002年6月之前购买的百事任何饮料的瓶盖上都会有一个百事球星的名字.只要凑齐所有百事球星的名字,就可参加百事世界杯之旅的抽奖活动,获得球星背包,随声听,更克赴日韩观看世界杯.还不赶 ...

  7. 转:浅谈深度学习(Deep Learning)的基本思想和方法

    浅谈深度学习(Deep Learning)的基本思想和方法  参考:http://blog.csdn.net/xianlingmao/article/details/8478562 深度学习(Deep ...

  8. [LOJ2538] [PKUWC2018] Slay the Spire

    题目链接 LOJ:https://loj.ac/problem/2538 Solution 计数好题. 首先可以发现这题和期望没关系. 其次对于手上的一套牌,设我们有\(a\)张强化牌,那么: 如果\ ...

  9. 运动员最佳匹配问题 KM算法:带权二分图匹配

    题面: 羽毛球队有男女运动员各n人.给定2 个n×n矩阵P和Q.P[i][j]是男运动员i和女运动员j配对组成混合双打的男运动员竞赛优势:Q[i][j]是女运动员i和男运动员j配合的女运动员竞赛优势. ...

  10. BZOJ4942 & UOJ314:[NOI2017]整数——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4942 http://uoj.ac/problem/314 https://www.luogu.or ...