C++对象模型解析一

来源:互联网 发布:com域名申请 编辑:程序博客网 时间:2024/05/26 05:51

关于对象

加上封装后所带来的布局成本

以下面一个c的结构体以及一个C++的类进行举例:

typedef struct point3d{float x;float y;float z;}Point3d;class Point3d{public:Point3d(float x=0,float y=0,float z=0):_x(x),_y(y),_z(z){}float x() { return _x;}float y() { return  _y; }float z() { return  _z;}private:float _x;float _y;float _z;}
对于c++,单纯的加上封装之后,内存布局并没有变化,每一个成员变量相对于对象起始地址的偏移都会在编译期被计算出来。跟c结构体内存布局一致,

值得注意的是每一个成员函数只会诞生一个函数实例,每一个内联函数都会在每一个使用它的模块身上产生一个函数实例(所以大的inline函数可能带来的代码膨胀)。

C++的布局以及存取时间上的主要额外负担是有virtual引起的:

1.虚函数机制:利用函数指针来支持一个有效率的执行器绑定。

2.虚基类机制:多次出现在继承体系中的基类有一个单一并且被共享的实例。

3.多重继承体系下,一个派生类到第二个后继之base class的转换。(从内存的角度考虑,第二个后继之base class在内存中的布局)


不带继承的基本c++对象模型

主要特点:

1.非静态的成员变量被存放在每一个类对象中,静态成员变量被放在具体对象之外。

2.静态的以及非静态的成员函数(非虚函数)被放在具体的类对象之外。

3.每一个class 都产生一对指向虚函数的指针 放在表格之中,这个表格就是vtbl(virtual  table)。

4.每一个具体的类对象都被安插了一个指向vtbl的指针,通常被称为vptr. vptr 的设定以及重置都由每一个类的构造函数 析构函数以及拷贝赋值运算符自动完成。每一个类所关联的type_info object(RTTI runtime type identification)也由虚表被指出来。通常放在第一个slot里面。(有了虚函数之后就要注意构造函数,析构函数,拷贝赋值运算符所带来的开销。)


以Point 类为例说明具体的内存布局:

class Point {public:Point (float xval);virtual ~Point();float x() const;static int PointCount();private:virual ostream& print(ostream &os) const;float x;static int _point_count;}


带继承的基本c++对象模型

带继承的C++对象模型主要带来的影响是 多重继承以及虚基类。详细的会在讨论三说明,强调的一点是base class subobject的数据成员被直接放在派生类对象中,并且不带任何的间接性。这提供了最有效率的存取(在编译期就决定了每个成员的offset)缺点是基类任何的改变都会导致派生类的对象的重新编译。

虚基类以及多从继承带来的内存模型的改变,会在讨论三中更清楚的说明。


以一段代码来解释对象模型如何来影响程序的效率:

X foobar(){X xx;X *px =new X;//foo()是一个虚函数xx.foo();px->foo();delete px;return xx;}//内部被转换为:void foobar(x& _result)//{_result.X::X();px=_new(sizeof(X));if(px!=0)px->X::X();foo(&_result);//扩展xx.foo() 但并不使用虚机制。(*px->vtbl[2])(px);if(px!=0)(*px->vtbl[1])(px);delete(px);return; }

C++ 以下列方式支持多态:

1.经由一组隐式的转换操作,例如把一个derived class 指针转换成一个指向其public base type的指针:

shape *ps=new circle();

2.经由virtual function机制

ps->rotate();

3.经由dynamic_cast和typeid运算符

if( circle *pc =dynamic_cast <circle*> (ps))...

指针的类型

每一种类型的指针存储的都是一个地址(在32位电脑上的整数是4bytes),那么指针的类型会教导编译器如何解释某个地址中的内存内容以及大小。所以我们无法知道一个void*类型的指针将涵盖怎么样的地址空间,所以所谓的转换其实是一种编译器指令,大部分情况下它并不改变一个指针所含的真正地址,它只影响被指出之内存的大小和其内容的解释方式。

struct与class的区别

观念上讲,如果C++要支持现存的C程序代码,它就不能不支持struct,引入class的原因在于,是引入了他所支持的封装和继承的的哲学,如果我们说base struct想着就是砌块,本来struct只体现数据的结构体。

我们可以主张说struct这个关键词的使用伴随着一个public接口的声明。

c的struct的与C++结合的方式就是复合,这样能保证c strcut的内存布局了。