c++对象模型function布局

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)第三种情况发生于一个语言扩充性质之下。详细略。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2025-01-02 03:12:05

c++对象模型function布局的相关文章

深度探索C++对象模型--Function语意学

成员函数调用方式 Nonstatic Member Function(非静态成员函数) -Nonstatic Member Function设计准则:与一般非成员函数(nonmember function)有相同的执行效率.因此,实际上编译器内部将Nonstatic Member 函数实体转化为对等的nonmember 函数实体. -转换过程: 1. 添加额外this指针,提供数据存取管道 2. 修改函数体内成员数据存取方式,改为使用this指针存取 3. 对函数名字重新"mangling&qu

C++对象模型(内存布局)

如果是一个空类,sizeof(A) = 1 C++的成员包含:非static数据成员,static数据成员,非static成员函数,static成员函数,virtual成员函数. 如类 class Person { public: Person():m_id(0),m_age(20) { ++m_count; } virtual ~Person() { --m_count; } virtual void print() { cout << "id:" << m_

C++ 对象的内存布局(上)

C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的.我在这里一并对大家的留言表示感谢.这也是我为什么再写一篇续言的原因.因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单.不想,这篇文章成为了打开C++对象模

对象内存布局 (14)

前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的.我在这里一并对大家的留言表示感谢.这也是我为什么再写一篇续言的原因.因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单.不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层次的讨论.当然,我之前的文章还有很多方面没有涉及,从我个人感觉下来,在谈论虚函数

html万能排版布局插件,web视图定位布局创意技术演示页

html万能排版布局插件,是不是感觉很强大,原理其实很简单,不过功能很强大哈哈,大量节省排版布局时间啊! test.html <!doctype html> <html> <head> <meta charset="utf-8"> <title>web视图定位布局创意技术演示页</title> <meta content="width=device-width,initial-scale=1.0,m

C++对象内存模型

C++ 对象的内存布局 陈皓 http://blog.csdn.net/haoel 前言 07年12月,我写了一篇<C++虚函数表解析>的文章,引起了大家的兴趣.有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的.我在这里一并对大家的留言表示感谢.这也是我为什么再写一篇续言的原因.因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单.不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更

Qt 学习之路 :菜单栏、工具栏和状态栏

在之前的<添加动作>一文中,我们已经了解了,Qt 将用户与界面进行交互的元素抽象为一种“动作”,使用QAction类表示.QAction可以添加到菜单上.工具栏上.期间,我们还详细介绍了一些细节问题,比如资源文件的使用.对象模型以及布局管理器.这一节则是详细介绍关于菜单栏.工具栏以及状态栏的相关内容. 我们假设窗口还是建立在QMainWindow类之上,这会让我们的开发简单许多.当然,在实际开发过程中,QMainWindow通常只作为“主窗口”,对话框窗口则更多地使用QDialog类.我们会在

jQuery版推箱子游戏详解和源码

前言 偶然间看到很多用js写游戏的感觉很炫酷的样子,所以就想试试,就看了一些资料和某前端站点的视屏.于是乎就自己动手实践了一下,上推箱子截图 感觉很丑陋,但是功能是实现了.再说貌似大多都是这样的吧,这一关其实还是有点难度的,我做完之后想检测一下下一关正确么,居然还玩了以后才通关. 如果你看到这张图让你想起了你童年的回忆,说明你老了,这里可以试玩一下(很遗憾没有链接地址,最后又源码可以下载). css布局 主要考虑的是地图是怎么动态生成的,地图中有灰色的,还有墙,箱子,蓝色,红色背景,人物.先看c

闭包相关概念

几天没有更新,这两天使周末,给大家整理了一几篇东西,有关于作用域的,闭包的,还有递归的,闭包和递归,对于大部分初次接触编程的人来说还是有些难度的,昨天,花了一点时间给大家整理了一下,今天,给大家上传上来,让大家看看,部分属于个人观点,如有错误,欢迎指出 这一篇给大家讲讲什么是闭包,闭包在很多语言中都是有的,Java,C#等都是有的,这里给大家讲讲JS中的闭包 1. 闭包 闭包的含义就是闭合,抱起来.简单的的来说就是一个具有封闭功能与包裹功能的一个结构,所谓的闭包就是 有一个具有封闭的对外不公开的