c++对象模型function布局

来源:互联网 发布:遗传算法基本思想 编辑:程序博客网 时间:2024/05/22 12:38

Function语意学

C++支持三种类型的memberfunctions:static、nonstatic和virtual,每一种类型被调用的方式都不相同。

Static函数有两个特点:它不能直接存取nonstatic数据;它不能被声明为const。

一Member的各种调用方式

1.NonstaticMember Functions

C++的设计准则之一就是:nonstaticmember function 至少必须和一般的nonmemberfunction有相同的效率。

如果normalize()是一个virtualmember function,那么一下调用:

ptr->normalize();

编译器的转化步骤为:

①改写函数的signature(函数原型)以安插一个额外的参数到memberfunction

中,用以提供一个存取管道,是classobject得意调用该函数。

②将对每一个“对nonstaticdata member的存取操作”改为经由this指针来存

取。

③将memberfunction重新写成一个外部函数。对函数名称进行“mangling”

处理,使它在程序中成为独一无二的语汇。

2.VirtualMember Functions

如果normalize()函数是一个virtualmember function,那么一下调用:

ptr->normalize()将被转化为(*ptr->vptr[1])(ptr);

其中:

n  vptr表示有编译器产生的指针,指向virtual table。它被安插在每一个“声明有(或继承自)一个或多个virtualfunctions”的classobject中。事实上其名称也会被“mangled”,因为在一个复杂的class派生体系中,可能存在多个vptrs。

n  1是virtualtable slot的索引值,关联到normalize()函数。

n  第二个ptr表示this指针。

而对于调用:

obj.normalize();

编译器却没必要也不会把它转化为:

(*obj.vptr[1])(&obj);

而是会将它当作一般的nonstaticmember function一样决议:

normalize_7Point3dFV(&obj);

3.StaticMember Function

如果Point3d::normalize()是一个staticmember function,一下两个调用操作:

obj.normalize();

ptr->normalize();

将被转化为一般的nonmember函数调用,像这样:

//obj.normalize();

normalize_7Point3dSFV();

//ptr->normalize();

normalize_7Point3dSFV();

对于下面的写法:

                                         ((Point3d*)0)->object_count();

其中object_count()只是简单的传回_object_count这个staticdata member。

在引入staticmember functions之前,在c++语言要求所有的memberfunctions都必须经由该class的object来调用。而实际上,只有当一个或多个nonstaticdata members在memberfunction中被直接存取时,才需要class object。classobject提供了this指针给这种形式的函数调用使用。如果没有任何一个members被直接存取,事实上就不需要this指针,因此就没必要通过一个classobject来调用一个member function。

staticmember functions的主要特性是它没有this指针。一下的次要特性统统根源于其只要特性:

n  它不能够直接存取其class中的nonstaticmembers。

n  它不能够被声明为const、volatile或virtual(隐含函数中有nonstaticmembers)。

n  他不需要经由classobject才被调用——虽然大部分时候它是这样被调用的。

和staticmember data类似,如果取一个staticmember function的地址得到的是其在内存中的地址。如:

&Point3d::object_count();

会得到一个数值,类型是:

unsignedint (*)();

而不是:

unsignedint (Point3d::*)();

Static member function由于缺乏this指针,因此差不多等同于nonmemberfunction。它提供了一个意想不到的好处:成为一个callback函数。

二Virtual Member Functions

我们已经看过了virtualfunction的一般实现模型:每一个class有一个virtualtable,内含该class之中的有作用的virtualfunction的地址,然后每个object有一个vptr,指向virtualtable的所在。

1.单重继承下的virtualfunction

一个class只会有一个virtualtable(单重继承)。每一个对应的class object中所有的activevirtual function函数实体的地址。这些activevirtual function包括:

n  这个class所定义的函数实体。它会重写(overriding)一个可能存在的baseclass virtual function函数实体。

n  继承自baseclass的函数实体,这是在derived class决定不重写virtualfunction时才会出现。

n  一个pure_virtual_called()函数实体,它既可以扮演purevirtual function的空间保卫角色,也可以当作执行期异常处理函数。

每一个virtualfunction都被指派一个固定点索引值,这个索引在整个继承体系中保持与特定的virtualfunction的关联。例如在我们的Point class体系中:

classPoint

{

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派生自Point时,例如classPoint2d:

class Point2d : public Point

{

public:

       Point2d(floatx = 0.0, float y = 0.0) : Point(x), _y(y) {}

       ~Point2d();

       //重写baseclass virtual functions

       Point2d&mult(float);

       floaty() const { return _y;}

       //......

protected:

       float_y;

};

       一共有三种可能性:

1)     它可以继承baseclass所声明的virtual functions的函数实体。正确的说,是该函数实体的地址会被拷贝到derivedclass的virtual table相对的slot中。

2)     它可以使用自己的函数实体(函数体重写)。这表示它自己的函数实体地址必须放在对应的slot之中。

3)     它可以加入新的virtualfunction。这时候virtual table的尺寸会增加一个slot,而新的函数实体地址会被放进该slot中。

Point2d的virtualtable在slot1中指出destructor,而在slot2中指出mult()(取代purevirtual function)。它自己的y()函数实体放在slot3。继承自Point的z()函数实体地址则放在slot4。

类似的情况,Point3d派生自Point2d,如下

classPoint3d : public Point2d

{

public:

       Point2d(float x = 0.0, float y = 0.0,float z = 0.0)

              : Point2d(x, y), _z(z) {}

       ~Point3d();

       //重写baseclass virtual functions

       Point3d& mult(float);

       float z() const { return _z;}

       //......

protected:

       float _z;

};

其virtualtable中的slot1防止Point3d的destructor,slot放置Point3d::mult()函数地址。slot3放置继承自Point2d的y()函数地址,slot4放置自己的z()函数地址。

现在对于式子:

              ptr->z();

那么,我们有足够的只是在编译时期设定virtualfunction的调用呢?

n  一般而言,我们并不知道ptr所指对象的真正类型。然而我知道,经由ptr可以存取到该对象的virtualtable

n  虽然不知道那个z()函数实体被调用,但我知道每一个z()函数地址都被放在slot4

唯一一个在执行期才能知道的东西是slot4所指到底是哪一个z()函数实体。

2.多重继承下的virtualfunction

在多重继承体系中支持virtualfunction,其复杂度围绕在第二个及后继的base class身上,以及“必须在执行期间调整this指针”这一点,一下class体系为例:

classBase1

{

public:

      Base1();

      virtual ~Base1();

      virtual void speackClearly();

      virtual Base1 *clone() const;

protected:

      float data_Base1;

};

classBase2

{

public:

      Base2();

      virtual ~Base2();

      virtual void mumble();

      virtual Base2* clone() const;

protected:

      float data_Base2;

};

 

classDerived: public Base1, public Base2

{

public:

      Derived();

      virtual ~Derived();

      virtual Derived* clone() const;

protected:

      float data_Derived;

};

“Derived支持virtual function”的困难度,统统落在Base2subobject身上,有三个问题需要解决,以此例而言分别是(1)virtualdestructor,(2)被继承下的Base2::mumble(),(3)一组clone()函数实体。

首先,我把一个从heap中配置而得的Derived对象的地址,指定给一个Base2指针

                           Base2* pbase2 = newDerived;

新的Derived对象的地址必须调整,以指向其Base2subobject。编译时会产生如下代码:

                           //转移以第二个baseclass

                           Derived* temp = newDerived;

                           Base2* pbase2 = temp? temp + sizeof(Base1) : 0;

如果没有这样的调整,指针的任何“非多态运用”(向下面那样)都将失败:

                           //即使pbase2被指定一个Derived对象,这也没问题

                           pbase2->data_Base2;

当程序员要删除pbase2所指的对象时:

                           //必须首先调用正确的virtualdestructor函数实体

                           //然后执行delete运算符

                           //pbase2可能需要调整,以指出完整对象的起始点

                           delete pbase2;

指针必须再一次被调整,以求再一次指向Derived对象的起始处(推测它还指向Derived对象)。然而上述的offset加法却不能够在编译时期直接设定,因为pbase2所指的真正对象只有执行期才能确定。

delete操作带来的“必要的this指针调整”操作必须在执行期完成。

在多重继承下,一个derivedclass内含n-1个额外的virtualtable。n表示其上一层baseclass的数目(因此单一继承不会有额外的virtual table)。对于本例会有两个virtualtable被编译器产生出来:

(1)   一个主要实体,与Base1(最左端baseclass)共享

(2)   一个次要实体,与Base2(第二个baseclass)有关

多重继承下virtualtable的布局如下。

对于图中所说的三种情况如下:

(1)   通过一个“指向第二个baseclass”的指针,调用derived class virtual

function例如:

Base2* ptr = new Derived;

delete ptr;

       从下图中,你可以看到调用操作的重点:ptr指向Derived对象的Base2subobject;为了能够正确执行,ptr必须调整指向Derived对象的起始地址。

(2)   通过一个“指向derivedclass”的指针,调用第二个base class中一个

继承而来的virtualfunction。自此情况下derived class指针必须再次调整,以指向第二个basesubobject。例如:

Derived* pder = new Derived;

//调用Base2::mumble();

//pder必须被向前调整sizeof(base1)个bytes

pder->mumble();

(3)第三种情况发生于一个语言扩充性质之下。详细略。


0 0
原创粉丝点击