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. 如何理解*p++

    后置递增运算符的优先级高于解引用运算符! *p++ 等价于 *(p++) 但是,我们*p++的求值结果不可理解为p+1指向的对象的值,而应该是p指向的对象的值. 这是由于后置++的特性引起的. *p+ ...

  2. Rescue(BFS时间最短 另开数组或优先队列)

    Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is described as a N * M ...

  3. http://www.cnblogs.com/120626fj/p/7545958.html

    1.本周PSP 2.本周进度条: 代码行,博文字数,用到的知识点 3.累计进度图 3.1累计代码折线图 3.2累计博文字数折线图 4.本周PSP饼状图

  4. 深入理解Java之数据类型

    一.概述 我们通过编程解决一个具体问题时,首先要做的工作是用各种“数据结构”表示问题中的实体对象,而后才能着手研究描述具体业务逻辑的算法.这也正印证了”程序 = 数据结构 + 算法“.而这里的数据结构 ...

  5. C#通过SC命令和静态公共类来操作Windows服务

    调用的Windows服务应用程序网址:http://www.cnblogs.com/pingming/p/5115304.html 一.引用 二.公共静态类:可以单独放到类库里 using Syste ...

  6. [CLR via C#]值类型的装箱和拆箱

    我们先来看一个示例代码: namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Array ...

  7. DELPHI enablecontrols,disablecontrols函数

    DisableControls方法是在程序修改或后台有刷新记录的时候切断数据组件,如TTABLE.ADOQUERY等等与组件数据源的联系.如果没有切断,数据源中只要一有数据的改动,尤其是批量改动的话, ...

  8. 【EF】Entity Framework Core 2.0 特性介绍和使用指南

    阅读目录 前言 获取和使用 新特性 项目升级和核心API变化 下一步计划 遗憾的地方 回到目录 前言 这是.Net Core 2.0生态生态介绍的最后一篇,EF一直是我喜欢的一个ORM框架,随着版本升 ...

  9. CentOS ACL

    ACL:访问控制列表(Access Control List). 一般来说权限是针对某一类用户设置的.例如:一个文件只有拥有者.组.其他用户三种设置方式,如果希望对某个指定的用户进行单独的权限控制,就 ...

  10. Android 动画之View动画效果和Activity切换动画效果

    View动画效果: 1.>>Tween动画通过对View的内容进行一系列的图形变换(平移.缩放.旋转.透明度变换)实现动画效果,补间动画需要使用<set>节点作为根节点,子节点 ...