C++对象模型之四 成员函数笔记
成员函数的调用方式
非静态成员函数:C++设计准则之一就是非静态成员函数至少和一般的非成员函数一样的效率。
所以编译器内化成非成员函数,其步骤 1改写函数的原型以安插一个额外的参数到成员函数中,用以提供一个存取管道,使类对象得以调用该函数。 这个参数为 this 指针。
Point3d::Mangitude( Point3d *const this);
2 将每个对非静态数据成员的存取操作改为经由this指针来存取。
{ return sqr( this->_x * this->_x+ this->_y * this->_y+ this->_z * this->_z); }
3 将成员函数重新写成一个外部函数,对函数名称进行 mangling 处理。
Extern mangitude __7Point3dFv ( regiester Piont3d *const this)
虚成员函数 register float mag = Magnitude(); è register float mag = ( *this->vptr[2]) (this);
1 编译器生成vptr指针;2 虚函数表中的索引值;3就是this 指针
静态成员函数 obj.Magnitude(); ptr->Magnitude(); 都会转化成 Magnitude__7Point3dSFv();
虚成员函数
如果类中有虚函数,就会有虚函数表指针(vptr),表格的地址,大小和内容都是编译掌握住了。
执行时要做的是在特定的虚函数表槽激活虚函数。虚函数包括:1 派生类改写基类的虚函数,2 继承基类的虚函数,3 纯虚函数。
Class Pont
{
public:
virtual ~Point();
virtual Point& mult(float)=0;
float x() const {return _x;}
virtual float y() const { return 0;}
virtual float x() const { return 0;}
protecetd:
point (float x = 0.0);
float _x;
};
当一个类派生自Point时,会发生什么?
1 可能它继承基类所声明的虚函数的函数实体,正确地说是该函数实体的地址会被拷贝到派生类的虚函数表相对应的槽中
2 可能 它可以使用自己的函数实体,者表示它自己的函数实体地址必须放在对应的槽中
3 可能它可以加入一个新的虚函数表,这时候虚函数表的尺寸会增大一个槽,而新的函数实体地址会被放进该槽中。
多重继承下得虚函数
复杂度围绕在第二个及后继的基类身上,以及必须在执行时调整this指针
class Base1{
public: Base1();
virtual ~Base1();
virutal void speckclearly();
virutal Base1 *clone() const;
protected: float data_Base1;
};
class Base2{
public: Base1();
virtual ~Base2();
virutal void mumble();
virutal Base1 *clone() const;
protected: float data_Base2;
};
class Derived :public Base1,public Base2
{
public:
Derived();
Virtual ~Derived();
Virtual Derived *clone() const;
Protected : float data_Derived;
};
Base2 *pbase2 = new derived;
è 调整: Derived *temp = new Derived; Base2 *pbase2 = temp ? temp + sizeof(Base1):0;
当第二个基类的指针调用派生类的虚函数时,必须在执行中完成调整。也就是offset
this+=sizeof(base1);
Derived::~Derived(this);
虚函数在虚继承下
class Point2d{
public: Point2d( float = 0.0, float = 0.0);
virtual ~Point2d();
virtual void mumble();
virtual float z();
protected: float _x , _y ;
};
class Point3d :public virtual Point2d{
public: point3d (float = 0.0 , float = 0.0 );
~Point3d();
float z();
protected: float _z;
};
由于Point2d和Point3d的对象不再相符,两者之间的转换也就需要调整this指针.
函数效率
未优化
优化
内联成员
4.70
0.08
友元函数
6.13
4.43
静态成员
6.13
4.43
非静态成员
6.13
4.43
虚函数
6.90
4.76
虚函数多继承
7.06
4.90
虚函数虚继承
7.07
5.44
指向成员函数的指针
取个非静态成员函数的地址,如果它不是虚函数,则得到的是在内存中真正的地址。不过需要绑定在某个对象地址上。使用一个成员函数指针,如果并不用于虚函数,多重继承,虚继承等情况不会比一般的指针成本高。
指向虚函数的指针
float (Point:: *pmf) () = &Point::z; //取虚函数的地址得到是在虚函数表的索引值
( * ptr->vptr[ (int) pmf ] ) (ptr); //通过调用会被内部转化为一个编译时期的式子
区分PMF调用的是虚函数还是非虚函数方法:
( ( ( int ) pmf ) & ~127 ) ? (* pmf ) ( ptr ) : ( *ptrèvptr[ ( int) pmf ] ( ptr ) );
指向多重继承和虚继承的指针
采用结构体
struct _mptr
{
int delta; //表示this指针的offset
int index;
union {
ptrtofunc faddr;
int v_offset; //虚函数在虚函数表的索引
};
};
( ptr-> *pmf) (); 会变成 (pmf.index < 0 )? ( pmf.faddr ) (ptr ): ( ptr->vptr[ pmf.index ]( ptr ) );
指向成员函数指针的效率
未优化
优化
非成员函数指针
6.12
4.30
成员函数指针
6.38
4.30
多重继承非虚函数指针
6.32
4.30
虚继承非虚函数指针
6.84
4.70
虚函数指针
7.39
4.70
多重继承虚函数指针
8.72
5.90
虚继承虚函数指针
8.80
5.84
内联函数
一般而言处理内联函数有两个阶段
1 分析函数定义,以决定函数的内联的本资。如果判断其不可内联就会转化成静态函数。
2 内联函数的展开是在调用点上,这样会带来参数求值和临时对象管理。
形式参数
inline int min( int I,int j) { return I<j ? I:j ;}
main()
{
int minval ;
int val1=1024;
int val2=2048;
minval = min (val1,val2);
minval = min (1024,2048);
minval = min (foo(),bar()+1);
}
第一调用的 minval = val1 <val2 ? val1 : val2;
第二调用的 minval = 1024;
第三调用的 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 ;
}
main()
{ int loval _var,minval;
minval = min (val1,val2);
}
就会被扩展为:
{ int local _var, minval ;
int__min_lv_minval;
minval = (__min_lv_minval = val1 < val2 ? val1: val2), __min_lv_minval;
}
一般而言,内联函数中的每个局部变量都必须被放在函数调用的一个封闭区间中,拥有独一无二的名称。
如果内联函数以单一表达式扩展多次,那么每次都需要一组局部变量。以分离多个式子被扩展多次,那么只需要一组局部变量,就可以重复使用了。
作者名:曾凡坤, 又名曾牧暗鲨,网名:大白鲨 2003-8-3