深度探索C++对象模型读书笔记(一)

一、加上封装之后的布局成本:

  在C语言中,“数据”和“处理数据的操作”(函数)是分开来声明的,语言本身并不具备“数据和函数”的关联。过程性的语言(典型代表为C)过程性是由一组“分布在各个以功能为导向的函数中”的算法所驱动。它们处理的是共同的外部数据。举例:

typedef struct point3d
{
  float x;
  float y;
  float z; } Point3d;

欲打印一个point3d,就需要定义一个函数,若要高效,甚至可以将此函数定义为宏函数,或者是直接在程序中完成其操作。

void print3d_print(const Point3d* pd)
{
      printf("(%g,%g,%g)",pd->x,pd->y,pd->z);
}

同理,某个点的坐标值也可以直接存取:Point3d pt;pt.x = 0.0;pt.y = 0.0;pt.z=0.0;(此处为了;能明显一点显示,将其转换成全角格式)由此可以看到,过程性的语言之间数据和函数是互相独立,之间并不存在某种特定的关系;

  在C++当中,Point3d可以采取独立的“抽象数据类型(“abstract data type,ADT”)来实现,即将数据和处理数据的操作(函数)封装在一个整体(类)中:

class Point3d
{
public:
Point3d(float x = 0.0,float y = 0.0,float z = 0.0):_x(x),_y(y),_z(z)    {}
float x() {return _x;} 
float y() {return _y;}
float z() {return _z;}
void x(float xval) {_x = xval;}
void y(float yval) {_y = yval;}
void z(float zval) {_z = zval;}
private:
float _x,_y,_z;                        
};
inline ostream& operator<<(ostream& os,const Point3d& pt)
{
        os<<"("<<pt.x()<<","<<pt.y()<<","<<pt.z()<<")";
};

  在上述C Struct中,定义一个Struct Object:Point3d d1与在C++ Class中,定义一个Class Object: Point3d d2,从C语言到C++,将处理数据的操作(函数)封装到Class中,布局成本是否增加,换句话说,在相同的环境下sizeof(d2)的值是否大于sizeof(d1)的值,答案是否定的。加上封装之后布局成本并没有增加,虽然成员函数在class的声明当中,但是它并没有出现在class object中,每一个非内联成员函数只会诞生一个函数实例。至于每一个“拥有零个或一个定义”的内联函数则在其每一个使用者身上产生一个函数示例。C++的这种封装性质,并不会对其空间和执行期带来不良后果;其实,C++的布局成本主要体现在:1、virtual function机制;2、虚继承时virtual base class;

二、C++对象模式:

  在C++中,数据成员(class data members)有2种:静态(static)和非静态(nonstatic),成员函数(class member function)有3中:静态(static)、非静态(nonstatic)和虚拟(virtual),考虑抽象以下一个类类型:

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

    float _x;
    static int _point_count;
};
    

在这个class Point中,如何模塑出各种数据成员和成员函数呢?

1)、简单对象模型

  在此模型中,每一个class object都是一系列的槽(slot),而这个槽当中存放的是每一个member(包含数据和函数)的地址,很容易得到一个class object的大小为:指针的大小乘class中声明的成员个数;问题1:为什么要采用此模型?答:此模型的优点:简单,容易设计。但是此模型的缺点也异常明显:空间和时间效率都很低;问题2:为什么class object要存放成员的地址?答:这么做的原因是在class object都是指针类型,对象的大小很容易计算,如果保存的直接是成员,那么成员的类型不确定;

2)、表格驱动模型:

 

  在此模型当中,为了对所有类的所有对象都有一致的表现形式,此模型吧所有与成员相关的信息都抽取出来,数据成员放在Member Data Table当中,成员函数放在Function Member Table当中,Function Member Table则是一列slot,每个slot指向一个成员函数,而Member Data Table中则直接存放数据成员。在此模型当中,每一个class实例出来的object在×86系统当中,都只占有8个字节的内存空间。但是这种表格驱动模型最终也没有实际应用于真正的C++编译器当中,但是Function Member table却成为后来支持Virtual function的一个有效的方案,在后来的编译器当中,virtual function都被保存在一系列的slot中。

3)、C++对象模型:
  

  在后来的C++对象模型当中,可以看到non-static Data Member被直接保存在对象当中;如果class中有虚拟函数,则会为对象模型在增加一个Vptr指针,此指针指向一个包含用以支持RTTI的type_info object以及一系列的Virtual function地址的表格,值得一提的是,在virtual table中,地址由低到高分别为:type_info 以及在类中按申明顺序排列的一系列虚函数的地址;至于成员函数(包括静态与非静态)以及静态数据成员都被散列在对象内存之外;

总结:在三种对象模型当中:

1、第一种简单对象模型:优点:简单明了;缺点:赔上了空间及执行期的效率;

2、第二种表格驱动模型:优点:较简单模型来说,提供了较大的弹性,因为在类中如果成员有变动(可能是增加,溢出或是更改),那么此模型生成的对象大小依然恒定。从另一方面来说,此模型实例化之后的对象也在空间上比简单对象模型要轻量;缺点:不论是访问数据成员还是成员函数,都多了一层间接性,需要指针的指针才能对具体的成员进行访问,执行期效率降低;

3、第三种也就是后来普遍C++编译器所采用的对象模型:优点:较前两种模型来说,此模型最大的不同是将成员函数,静态数据成员都放在对象之外,当要用到这些成员时,通过一个this指针将对像和成员进行关联。此外,在对象中,通过一个vptr实现了RTTI以及后来的多态的特性。在执行效率以及空间上都做了一些改进。缺点:弹性限度较表格模型来说不够,因为如果类成员发生变动,那么随之而来的对象也要发生改变,要进行重新编译。

posted on 2018-08-22 22:57  NewDelete  阅读(119)  评论(0编辑  收藏  举报