4.1Member的各种调用方式
Nonstatic Member Functions(非静态成员函数)
C++的设计准则之一就是:nonstatic member function 至少必须和一般的nonmember function有相同的效率
名称的特殊处理
一般而言,member的名称前面会被加上class的明后才能,形成独一无二的命名,也就是mangling手法。对于member function由于可以被重载化,所以也需要mangling手法。
如magnitude()可能被转变为extern magnitude_7Point3dFv(register Pointed3d *const this);
用对象和用指针调用
obj.magnitude();
//变为
magnitude_7Pointed3dFv(&obj);
ptr->magnitude();
//变为
magnitude_7Pointed3dFv(ptr);
virtual member Function(虚拟成员函数)
用指针调用虚函数的步骤
如:ptr->normalize();
可能会被转化为(*ptr->vptr[1])(ptr);
1.vptr表示由编译器产生的指针,指向virtual table.它被安插在每一个“声明有(或继承自)一个或多个virtual functions"的class object中,事实上其名称也会被“mangled”,因为在一个复杂的派生体系中,可能存在有多个vptrs.
2.1是virtual table slot的索引值,关联到normalize()函数
3.第二个ptr表示this指针
用类对象调用虚函数,会被编译器像对待一般的nonstatic member function一样地加以决议
Static Member Functions(静态成员函数)
用指针和用对象调用是一个static member function,如Point3d::normalize(),都会被转换成一样的调用方式,如
obj.normalize();
ptr->normalize();
//变为
normalize_7Point3dSFv();
如果取一个static member function地址,获得的将是其在内存中的位置,也就是其地址。 由于static member function没有this指针,所以其地址类型并不是一个“指向class member function”的指针,而是一个“nonmember函数指针”
如&Point3d::object_count();
会得到一个数值,类型是:unsigned int(*)();
而不是unsigned int(Point3d::*)();
4.2Virtual Member Function(虚拟成员函数)
有虚函数的类对象都会包含一个虚指针指向虚函数表
编译器:
1.为了找到表格,每一个class object被安插上一个由编译器内部产生的指针,指向该表格
2.为了找到函数地址,每一个virtual function被指派一个表格索引值
执行期:
在特定的virtual table slot中激活virtual function。一个class只会有一个virtual table,每一个table内含其对应的class object中所有的active virtual functions函数实体的地址。这些active virtual functions包括:
1.这个class所定义的函数实体。他会改写一个可能存在的base class virtual function函数实体
2.继承自base class的函数实体。这是在Derived class决定不该写virtual function时才会出现的情况
3.一个pure_virtual_called()函数实体(纯虚函数),它既可以扮演pure virtual function的空间保卫者角色,也可以当做执行期异常处理函数(有时候会用到)
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;
};
class Point2d:public Point
{
public:
Point2d(float x=0.0,float y=0.0)
:Point(x), _y(y){}
~Point2d();
Point2d& mult(float);
virtual float y()const
{
return _y;
}
protected:
float _y;
};
class Point3d :public virtual Point2d
{
public:
Point3d(float x = 0.0, float y = 0.0,float z=0.0)
:Point2d(x,y), _z(z){}
~Point3d();
Point3d& mult(float);
float z()const
{
return _z;
}
protected:
float _z;
};
Point ,Point2d,Point3d在VS2013中的布局分别为
对于派生类
1.它可以继承base class所声明的virtual functions的函数实体,正确地说,是该函数实体的地址会被拷贝到Derived class的virtual table相应的slot之中
2.他可以使用自己的函数实体,这表示它自己的函数实体地址必须放在对应的slot之中
3.他可以加入一个新的virtual function。这时候virtual table的尺寸会增大一个slot,而新的函数实体地址会被放进该slot之中。
如果有ptr->z();
1.一般而言,我并不知道ptr所指对象的真正类型,然而我们知道经由ptr可以存取到该对象的virtual table;
2.虽然我们不知道哪一个z()函数实体会被调用,但是我们知道每一个z()函数地址都被放在slot 4
这些信息使得编译器可以将该调用转化为
(*ptr->vptr[4])(ptr);
唯一一个在执行期才能知道的东西是:slot 4所指的到底是哪一个z()函数实体
多重继承下的Virtual Function
在多重继承中支持virtual functions,其复杂度围绕在第二个以及后继的base class身上,以及“必须在执行期调整this指针”这一点。
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;
};
对于
Base *pbase1 = new Derived;
Base2 *pbase2 = new Derived;
delete pbase1;
delete pbase2;
虽然两个delete操作导致相同的derived destructor,但它们需要两个不同的virtual table slots;
1.pbase1不需要调整this指针(因为Base1是最短base class之故,它已经指向Derived对象的起始处)。其virtual table slot需要放置真正的destructor地址
2.pbase2需要调整this指针,其virtual table slot需要相关的thunk地址
在多重继承之下,一个Derived class内含n-1个额外的virtual tables,n表示其上一层base classes的数目。对于本例而言,derived class有2个virtual tables
1.一个主要实体,与Base1共享
2.一个次要实体,与Base2有关
VS2013中的布局与书上有些许不同
用一个指向Derived对象的Base1指针或Derived指针时,被处理的是主要实体,用一个指向Derived对象的Base2指针时,被处理的是次要实体
虚拟继承下的virtual functions
class Point2d
{
public:
Point2d(float x = 0.0, float y = 0.0);
virtual ~Point2d();
virtual void mumble();
virtual float z();
protected:
float _x,_y;
};
class Point3d :public virtual Point2d
{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0);
~Point3d();
Point3d& mult(float);
float z();
protected:
float _z;
};
VS2013中的布局
4.4指向Member Function的指针
一个指向member function的指针,其声明语法如下
double(Point::* pmf)();
定义并初始化
double(Point::*coord)()=&Point::x;
调用
(orginal.*coord)();
(ptr->*coord)();
支持“指向Virtual Member Functions”之指针
对于
float (Point::*pmf)()=&Point::z;
Point *ptr = new Point3d
对一个nonstatic member function取其地址,将获得该函数在内存中的地址,而对一个virtual function,其地址在编译期是未知的,仅知道其在virtual table 中的索引值,所以对一个virtual function取地址得到的是一个索引值。如果上述z()是虚函数,则
ptr->*pmf会被转换为
(*ptr->vptr[(int)pmf])(ptr);
多重继承之下,指向Member Functions的指针
为了让member functions的指针也能够支持多重继承和虚拟继承,设计了如下结构体
struct _mptr{
int delta;//表示this指针的offset值
int index;
union{
ptrtofunc faddr;
int v_offset;//存放一个virtual(或多重继承的第二个或后继的)base class的vptr位置
};
};
index和faddr分别带有virtual table
(ptr->*pmf)();
//变为
(pmf.index<0)?(*pmf.faddr)(ptr):(*ptr->vptr[pmf.index](ptr));
索引和nonvirtual member function地址,当index=-1时,index不指向virtual table
4.5Inline Functions
一般而言,处理一个inline函数,有两个阶段
1.分析函数定义,以决定函数的“instrinsic inline ability”(本质的inline能力)
2.真正的inline函数扩展操作是在调用的那一点上,这会带来参数的求值操作以及临时对象的管理
形式参数
在inline函数扩展期间,每一个像是参数都会被对应的实际参数取代。
1.常量表达式,在替换之前完成求值操作,后继的inline替换,就可以把常量直接“绑”上去
2.对于会被多次求值的实际参数,会引入临时对象
3.既不是常量表达式,也不是带有副作用的表达式,直接替换
inline int min(int i,int j)
{
return i<j?i:j;
}
inline int bar()
{
int minval;
int val1=1024;
int val2=2048;
/*(1)*/minval=min(val1,val2);
/*(2)*/minval=min(1024,2048);
/*(3)*/minval=min(foo(),bar()+1);
return minval;
}
(1)参数直接代换
minval=val1<val2?val1:val2;
(2)直接拥抱常量
minval=1024;
(3)引发副作用,导入临时对象
int t1;
int t2;
minval=(t1=foo()),(t2=bar()+1),t1<t2?t1:t2;
局部变量
如果inline函数中有局部变量
inline int min(int i,int j)
{
int minval=i<j?i:j;
return minval;
}
int local_var;
int minval;
//....
minval = min(val1,val2);
//变为
int loca_var;
int minval;
//将inline函数的局部变量处以“mangling”操作
int _min_lv_minval;
minval=(_min_lv_minval=val1<val2?val1:val2),_min_lv_minval;
inline函数中的局部变量再加上有副作用的参数,可能会导致大量的临时性对象产生。
inline函数如果被调用太多次的话,会产生大量的扩展码,是程序的大小暴涨。