C++对象模型之function语意学

一、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中的索引值。

猜你喜欢

转载自blog.csdn.net/qq_32164245/article/details/80880358
今日推荐