第四章 function语意学(一)

4-1 Member的各种调用方式


Nonstatic Member Functions(非静态成员函数)

     非静态成员函数被内化成非成员函数的形式,步骤如下

     1.改写函数签名,将this指针作为额外参数,如果成员函数是const函数,要变const class *const this

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

     3.将成员函数重新写成一个外部函数,对函数名称进行"mangling"处理

注:manling:name mangling是一种在编译过程中,将函数、变量的名称重新改编的机制。在C++重载,namespace等操作符下,函数一个有相同的名字,编译器为了区分各个不同地方的函数,将各个地方的函数重新改编

//对于函数
float Point3d::magnitude() const
{
     return sqrt(x*x+y*y+z*z);
}
//第一步和第二步改写为
float Point3d::magnitude(const Point3d const*_this) const//因为是const函数,所以Point3d前面要加const
{
      return sqrt(_this->x*_this->x+_this->y*_this->y+_this->z*_this->z);
}
//第三步manglint改写为
extern magnitude_7Point3dFv(register Point3d* const this);//magnitude是函数名,7是编号,Point3d类名,Fv是返回值类型和参数列表类型float、void

Virtual Member Functions虚拟成员函数

Point3d Point3d::normalize()cosnt
{
    register float mag=manitude;
    //....
   
}

//如果normalize()是virtual function,则
ptr->normalize()
//被转化为
(*ptr->vptr[1])(this);


其中,vptr为指向virtual table的指针

          1为normalize在该virtual table中索引值

何时发生这种转换?——在必需的时候。当通过指针调用的时候,要调用的函数实体无法在编译期决定,必需待到执行期才能获得,所以上面引入一个间接层的转换必不可少。但是当我们通过对象(不是引用,也不是指针)来调用的时候, 进行上面的转换就显得多余了,因为在编译器要调用的函数实体已经被决定。此时调用发生的转换,与一个非静态成员函数(Nonstatic Member Functions)调用发生的转换一致。


Static Member Functions(静态成员函数)

1.不能直接存取其class中的nonstatic members

2.不能被声明为const、volatile、或virtual

3.不需要经由class obejct才被调用

//static function object_count()简单的传回_object_count这个static member
//可以直接通过class调用
if(Point3d::object_count()>1)
//如果class object是因为某个表达获得
if(foo.object_count()>1)
//则foo仍会被表达求值(evaluted)

4-2 虚拟成员函数 Virtual Member Functions

c++中的多态:以一个public base class的指针寻址出一个derived class object

执行期多态(消极的):virtual function调用需要相关对象在执行期的某些信息

编译器多态(积极的):a.当被指出的对象真正被使用时

                                      b.RTTI:dynamic_cast

if(Point3d *p3d=dynamic_cast<Point_3d*>ptr)
     return p3d->z;


正确调用执行虚拟成员函数的方法:


对于

Point3d *ptr;
ptr=new Point2d;
ptr->z();

实现,在每个class object上增加两个Members

1.ptr所指对象的真实类型:一个字符串或数字,表示class的类型

2.z()实体位置:一个指针,指向某个vtbl,vtbl中带有程序的virtual functions的执行期地址

每个class只有一个virtual table

虚函数的地址在可以在编译期获知的,而且这一地址是固定不变的。而且表的大小不会在执行期增大或减小。


解决方案: 

      1.为找到表格,每一个class object被安插上一个由编译器内部产生的指针vtpr,指向虚函数表

      2.未找到函数地址,每一个virtual function被指派一个表格索引值。


虚函数表中的内容:

     1.这个class定义的函数体。它会改写(overriding)一个可能存在的base class virtual function

     2.继承自base class的函数实体(未改写),直接继承

     3.一个纯虚函数,没有定义,在虚函数表中占用一行,有时候也可以当做执行期异常处理函数。

每一个函数都被指派一个固定的索引值

例:

class Point
{
    public:
        virtual ~Point();
        virtual Point &mult(float)=0;
        //....
        float x() const{return x;}
        virtual float y() const{return 0;}
        virtual float z() const{return 0;}
        //...
protected:
       Point(float x=0.0);
       float _x;
}
virtual ~Point()被赋值为slot 1

mult()被赋值slot 2(纯虚函数)

y()被赋值为slot 3

z()被赋值为slot 4

x()不是virtual function,故不再其中

class Point2d:public Point
{
public:
    Point3d(float x=0.0,float y=0.0):_y(y),_z(z){}
    ~Point3d();
    
    //改写base class的虚函数
    Point3d& mult(float);
    float y()const{return _y;}
    //...
    
protected:
    float _z;
};

class Point3d:public Point2d
{
public:
    Point3d(float x=0.0,float y=0.0,float z=0.0):Point2d(x,y),_z(z){}
    ~Point3d();
    
    //改写base class的虚函数
    Point3d& mult(float);
    float z()const{return _z;}
    //...
    
protected:
    float _z;
};



对于一个派生类,他的虚函数有下列两种情况

1.对于继承而来的虚函数

  a.可以继承基类的虚函数函数实体,该函数体地址被拷贝到派生类的virtual table相对应的slot中

  b.可以使用自己的函数实体,函数实体地址必须放在对应slot中

2.可以加入一个新的virtual function,此时virtual table增大一个尺寸,新的函数实体地址被放进该slot中


编译期所做的工作

//将
ptr->z();
//转化为
*ptr->vtpr[4](ptr);

执行期就将相应slot中的virtual function激活


4-3多重继承下的虚函数

class Derived	size(20):
         	+---
         	| +--- (base class Base1)
          0	| | {vfptr}
          4	| | data_Base1
         	| +---
         	| +--- (base class Base2)
          8	| | {vfptr}
         12	| | data_Base2
         	| +---
         16	| data_Drived
         	+---

多重继承下要考虑的主要是指针的调整问题,如上图


指针的调整有如下两种情况:

1.调整指针指向对应的subobject,一般发生在继承类类型指针向基类类型指针赋值的情况下。

Base2 *pbase2=new Derived;
编译期产生如下码来指向Base2 subobject

//转移以支持第二个base classes
Derived *temp = new Derived;
Base2 *pbase2 = temp?temp+sizeof(Base1):0;//temp为空
2.将指向subobject的指针调整回继承类对象的起始点,一般发生在基类指针对继承类虚函数进行调用的时候。

delete pbase2;
此时调整指针,指向Derived对象的起始处

对第一种情况:

a.将virtual table增大,是它容纳此处所需的this指针,每一个virtual table slot中内含可能的offset及地址

//操作
(*pbase2->vptr[1])(pbase2);
//改变为
(*pbase2->vptr[1].faddr)(pbase2 + pbase2 -> vptr[1].offset);
//其中faddr内含virtual funtion地址,offset内含this指针调整值
b.Thunk技术

pass-by-name,支持(1)以适当的offset值调整this指针 (2)调到virtual function去

Thunk技术允许你virtual table slot继续内含一个简单的指针,对那些需要调整this指针的virtual function,则直接指向thunk,否则他所在的所在的slot中的地址可以直接指向virtual function。


c.split functions技术

在函数被认为足够小的时候使用,以相同的算法产生两个函数。为左边的base class(和Derived class的起始地址相同)不需要调整返回值;而通过右边的base class调用时,要机上一个offser,调用的就是另一个函数


第二种情况下,this指针的调用可能由多种可能,在多重继承下,一个derived class内含n-1个额外的virtual tables,derived class中存在vptr指向每一个virtual table

class Derived	size(20):
         	+---
         	| +--- (base class Base1)
          0	| | {vfptr}
          4	| | data_Base1
         	| +---
         	| +--- (base class Base2)
          8	| | {vfptr}
         12	| | data_Base2
         	| +---
         16	| data_Drived
         	+---
还是看到这张图,第一个virtual table与Base1(最左端共享),第二个为次要实体,和Base2有关。

由于一个class有多个virtual table,那么需要给出第一无二的外部名称以示区分

vtbl_Derived;
vtbl_Base2_Derived;

那么

Base1* pbase1= new Derived;//Base1类型指针,指向vtbl_Derived
Base2* pbase2=new Derived;//Base2类型指针,指向vtbl_Base_Deried;

delete pbase1;
delete pbase2;
//就能根据指向的表格调用相应的slot中的函数

此外,还会有第三种情况:

一个virtual function 的返回值类型有所变化,可能是Base类型,也可能是public derived类型。

Base2 *pb1=new Derived;//pb1指向Derived中的base2 subobject

//调用Derived* Derived::clone();
//返回值必须被调整,以指向Base2 subobject
Base *pb2=pb1->clone();
      
      当进行pb1->clone(),pb1会被调整指向Derived对象的起始地址,就像情况2描述的那样。

      然后传回一个指向Derived对象的指针,赋给pb2,如情况1所描述,调整指针指向对应的Base2 subobject


4-3函数的效能


1、4-1中提到,非静态成员数会被内化成非成员函数的形式,所以nonmember或static member 或nosntatic member函数被转化为完全相同的形式,所以三者效率完全相同

2、编译器将inline函数视为“不变的表达式”,提到循环之外,因此只计算一次。故inline函数可以节省一般函数所带来的额外负担,也可进行程序优化

3、virtual函数效率明显比上述集中函数低很多

     原因a.构造函数中对vptr的设定

            b.需要一个offset以调整this指针,指向放置于virtual table中的适当地址(在thunk中,this指针的调整成本被局限于有必要那么做的函数中)

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值