一、nonstatic member function(非静态成员函数)
c++设计准则之一:nonstatic member function至少要和一般的nonmember function有相同的效率;对于如下的两个函数:
float magnitude3d(const Point3d *_this) {...}//非成员函数
float Point3d::magnitude3d()const {...}//成员函数
非成员函数的定义:
float magnitude3d(const Point3d *_this)
{
return sqrt(_this->x *_this->x + _this->y *_this->y + _this->z *_this->z );
}
来看看非静态成员函数被内化为nonmember的步骤:
1、改写函数原型,安插this指针到member function中。
//non-const nonstatic memberde 扩张过程
Point3d Point3d::magnitude(Point3d *const this)
//如果member function是const的,const nonstatic member变成
Point3d Point3d::magnitude(const Point3d *const this)
2、将每一个“对nonstatic data member的存取操作”改为经由this指针来存取
{
return sqrt(this->x *_this->x + this->y *_this->y + this->z *_this->z );
}
3、将member function重新写成一个外部函数,函数名称经过“mangling”处理,
例如:
extern magnitude_7Point3dFv(register Point3d *const this);
因此,
obj.magnitude();
变成了:
magnitude_7Point3dFv(&obj);
而
ptr->magnitude();
成为:
magnitude_7Point3dFv(ptr);
二、virtual member function(虚拟成员函数)
如果normalize()是一个virtual member function,那么 ptr->normalize();
将会被内部转化为
(*ptr->vptr[1])(ptr);
- vptr是编译器产生的指针,指向virtual table;
- 1是virtual table slot的索引值,关联到normalize()函数;
- 第二个ptr表示this指针。
三、static member functions静态成员函数
取一个static member function的地址,获得的将是其在内存中的位置,也就是其地址。static member function没有this指针,所以其地址的类型并不是一个“指向其class member function的指针”,而是一个“nonmember函数指针”。
例如下列的静态成员函数:
&Point3d::object_count();
会得到一个数值,类型是:unsigned int(*)();
而不是:unsigned int(Point3d::*)( );
四、virtual member function(虚拟成员函数)
在c++中,多态表示“以一个public base class的指针或者引用,寻址出一个derived class object”的意思。
识别一个class是否支持多态,唯一适当的方法就是看看它是否有任何virtual function。只要class拥有一个virtual function,就需要额外的执行期信息。
1、单一继承
单一继承情况下,一个class只会有一个virtual table。
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;
};
注:pure_virtual_called()扮演pure virtual function的空间保卫者角色,也可以当做执行期异常处理函数;
class Point2d : public Point
{
public:
Point2d(float x = 0.0,float y = 0.0) : Point(x),_y(y) {}
~Point2d();
//改写base class virtual functions
Point2d& mult(float);
float y() const {return _y;}
//...
protected:
float _y;
};
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 virtual functions
Point3d& mult(float);
float z() const {return _z;}
//...
protected:
float _z;
};
假设现在有
Point* ptr; ptr->z();
在编译期设定virtual function的调用:
- 每次调用z()时,并不知道ptr所指对象的真正类型,但是知道ptr可以存取到该对象的virtual table;
- 有virtual table知道,每一个z()函数地址都被放在slot 4中,所以,编译器会将该调用转化为:
(*ptr->vptr[4])(ptr);
唯一一个在执行期才能知道的是:slot 4所指的到底是哪一个z()函数实例。
2、多重继承
多重继承下的virtual functions,难度在于第二个及后继的base classes身上,以及“必须在执行期调整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;
};
试想: Base2 *pbase2 = new Derived;
新的Derived对象的地址必须调整以指向其Base2 subobject,在编译时期会产生以下代码:
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0 ;
在多重继承下,一个derived class内含n-1个额外的virtual tables,n表示base class个数。
下面的操作:
Base1 *pbase1 = new Derived;
Base2 *pbase2 = new Derived;
delete pbase1;
delete pbase2;
虽然delete操作导致相同的Derived destructor,但他们需要两个不同的virtual table slots:
- pbase1不需要调整this指针,因为Base1是最左端的base class,已经指向Derived对象的起始处,其virtual table slot需放置真正的destructor地址。
- pbase2需要调整this指针。
对于上述图片,有三种情况:
(1)、“指向第二个base class的指针”,调用derived class virtual functions。
Base2 *ptr = new Derived;
//调用Derived::~Derived,ptr 必须向后调整sizeof(Base1)个bytes。(在图片中,应为向上)
delete ptr;
ptr指向Derived对象中的Base2 subobject。为正确执行,ptr必须调整指向Derived 对象的起始处。
(2)、通过指向Derived class的指针,调用第二个base class中继承而来的virtual function。
Derived *pder = new Derived;
//调用Base2::mumble(),pder必须被向前调整sizeof(Base1)个bytes(在图片中,应为向下)
pder->mumble();
(3)、指向第二个base class的指针来调用clone()时,调整this 的offset
Base2 *pb1 = new Derived;
//调用Derived * Derived::clone(),返回值必须被调整,以指向Base2 subobject
Base2 *pb2 = pb1->clone();
当进行pb1->clone()时,pb1会被调整指向Derived 对象的起始地址,于是clone()的Derived版会被调用:它会传回一个指针,指向一个新的Derived对象;该对象的地址在被指定给pb2之前,必须经过调整,以指向Base2 subobject;
3、虚拟继承下的virtual function
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,float = 0.0)
~Point3d();
float z();
//...
protected:
float _z;
};
由于Point2d和Point3d的对象不再相符,两者之间的转换也就需要调整this指针。
对于mumble函数有争议,书上讲的悬乎,但是给出了建议:
不要在virtual base class中声明nonstatic data members.
4、指向member function 的指针
取一个nonstatic data member的地址,得到的结果是该member在class布局中的byte位置(再加1);
取一个nonstatic member function的地址,若该函数是nonvirtual的,得到的结果是它在内存中真正的地址,但是它需要被绑定于某个class object的地址上,才能够调用该函数,所有的nonstatic member function度需要对象的地址。
取一个virtual member function的地址,将获得的是vtbl中的索引值。