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指针的调整成本被局限于有必要那么做的函数中)