深度探索C++对象模型 个人总结 第一章 关于对象

第一章 关于对象

C语言并不支持“数据和函数”间的关联,因此是程序性的,在C++中则可以以用“一个ADT(抽象数据类型)或class hierarchy的数据封装”来替代“C程序中程序性地使用全局数据”,而且加上封装后并不必然增加布局成本,因为C++在布局以及存取时间上主要的额外负担是由virtual引起的。包括:vitrual function机制和virtual base class,此外还有一些多重继承下的额外负担,发生在“一个derived class 何其第二或后继之base class的转换”之间。

1.1C++对象模式

简单对象模型(A Simple Object Model):

    一个object是一系列的slots,每一个slot指向一个members,Members按其声明顺序,各被指定一个slot,每一个data member或function member都有一个slot,在该模型下slot只存放指向member的指针,避免因为member有不同类型因而需要不同存储空间,它降

    低了C++编译器的设计复杂度,但却赔上了空间和执行期的效率,因此该模型并没有被应用于实际产品上,但却应用到C++的“指向成员的指针”观念中

表格驱动对象模型(A Table-driven Object Model):

    为了对所有classes的所有objects都有一致的表达方式,模型把与members有关的信息抽出来,放在data member table(直接持有data本身)和member function table(一系列的slots,每一个slot指出一个member function)中,class object本身则内含指向这两个

    表格的指针该模型并没有被应用于实际产品上,但却应用到C++的“指向成员的指针”观念中

C++对象模型(The C++ Object Model):

    C++对象模型从简单对象模型派生而来,对内存空间和存取时间做了优化。Nonstatic data members放在class object之内,static data members、static function members和nonstatic function members都被放在个别的class object之外,virtual function则由class产

    生一堆指向virtual functions的指针,放在virtual table(vtbl)之中,每个class object都有一个指向vtbl的指针(vptr),vptr的设定和重置都由每一个类的构造、析构和拷贝赋值运算符自动完成。每一个类所关联的type_info对象(支持运行期类型识别——RTTI)也

    经由virtual table指出,放在表格的第一个slot。相比起双表格模型,如果应用程序代码本身未曾改变,但用到的class object的nonstatic data member有所改变,那么应用程序代码同样得重新编译。

    

    C++支持单一继承、多重继承,继承关系可以指定为virtual,虚拟继承下base class不管继承串链被派生多少次永远只存在一个实例

    单继承, 无虚继承时的对象模型

      这是最简单的情况, 在对象的开始处保存一个vptr指针, 指向一个虚函数指针数组, 非静态数据成员按继承, 声明的顺序排列

class BB
{
    int64_t m_bb;
};
class B1 : public BB
{
    int64_t m_b1;
};
class DD : public B1
{
    int64_t m_dd;
};
class D1 : public DD
{
    int64_t m_d1;
};


class D1    size(32):
    +---
 0    | +--- (base class DD)
 0    | | +--- (base class B1)
 0    | | | +--- (base class BB)
 0    | | | | m_bb
    | | | +---
 8    | | | m_b1
    | | +---
16    | | m_dd
    | +---
24    | m_d1
    +---

   

    单继承, 有虚继承时的对象模型

      采用虚继承的类会在产生多个vptr, 对象开始处是父类的vptr, 父类成员之后, 子类成员之前保存子类的vptr

class BB
{
    int64_t m_bb;
};
class B1 : public virtual BB
{
    int64_t m_b1;
};
class DD : public B1
{
    int64_t m_dd;
};
class D1 : public DD
{
    int64_t m_d1;
};


class D1 size(40):
+---
0    | +--- (base class DD)
0    | | +--- (base class B1)
0    | | | {vbptr}
8    | | | m_b1
| | +---
16    | | m_dd
| +---
24    | m_d1
+---
+--- (virtual base BB)
32    | m_bb
+---
D1::$vbtable@:
0    | 0
1    | 32 (D1d(B1+0)BB)
vbi:    class offset o.vbptr o.vbte fVtorDisp
BB 32 0 4 0

    多继承, 无虚继承时的对象模型

      保留多个父类的vptr    

class BB
{
    int64_t m_bb;
};
class B1 : public BB
{
    int64_t m_b1;
};
class B2 : public BB
{
    int64_t m_b2;
};
class DD : public B1, public B2
{
    int64_t m_dd;
};

class DD size(40): +--- 0 | +--- (base class B1) 0 | | +--- (base class BB) 0 | | | m_bb | | +--- 8 | | m_b1 | +--- 16 | +--- (base class B2) 16 | | +--- (base class BB) 16 | | | m_bb | | +--- 24 | | m_b2 | +--- 32 | m_dd +---

    多继承, 有虚继承时的对象模型

      相当于上面的结合

class BB
{
    int64_t m_bb;
};
class B1 : public virtual BB
{
    int64_t m_b1;
};
class B2 : public virtual BB
{
    int64_t m_b2;
};
class DD : public B1, public B2
{
    int64_t m_dd;
};


class DD    size(48):
    +---
 0    | +--- (base class B1)
 0    | | {vbptr}
 8    | | m_b1
    | +---
16    | +--- (base class B2)
16    | | {vbptr}
24    | | m_b2
    | +---
32    | m_dd
    +---
    +--- (virtual base BB)
40    | m_bb
    +---
DD::$vbtable@B1@:
 0    | 0
 1    | 40 (DDd(B1+0)BB)
DD::$vbtable@B2@:
 0    | 0
 1    | 24 (DDd(B2+0)BB)
vbi:       class  offset o.vbptr  o.vbte fVtorDisp
              BB      40       0       4 0

对象模型如何影响程序(How the Object Model Effects Programs)

    不同的对象模型会导致“现有的程序代码必须修改”以及“必须加入新的程序代码”两种结果

1.2关键词所带来的差异

关键词的困扰

什么时候一个人一个人应该在C++程序中以struct取代class?

      由于在C++中struct本身并没有什么用,所以如果你觉得这样做比较好的时候(你可以主张说它伴随着一个public接口的声明,或者说只是为了方便C程序员迁徙到C++部落中)

引入class关键词的作用是什么?

      这个语言所引入的不只是关键词,还有它所支持的封装和继承的哲学

策略性正确的struct

怎么在c++中用好struct?

      程序员迫切需要C++ class的某部分数据拥有C声明的模样,此时将这一部分抽出来成为一个独立的struct声明,将struct和class组合起来,而非继承,才是把C和C++结合在一起的唯一可行的方法。struct声明可以将数据封装,并保证拥有与c兼容的空间布局。

1.3对象的差异

C++程序设计模型直接支持三种programming paradigms(程序设计范式)

程序模型

      C++就像C一样支持它

抽象数据类型模型

      此模型所谓的抽象是和一组表达式(public 接口)一起提供的,那时其运算定义式仍然隐而未明

面向对象模型

      在此模型中有一些彼此相关的类型,通过一个抽象的base class被封装起来。

    纯粹以一种paradigm写程序有助于整体行为的良好稳固

    C++中对于object的多态操作只有通过pointer或者reference的间接处理,才能支持多态。

C++以下列方法支持多态(主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中)  

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

      2.经由virtual function机制

      3.经由dynamic_cast和typeid运算符

class object内存一般有:

      其nonstatic data members的总和大小;

      加上任何由于alignment(译注)的需求而填补(padding)上去的空间(可能存在于members之间,也可能存在于集合体边界);(译注:alignment就是将数值调整为某数的倍数。在32位计算机上,通常alignment为4bytes(32位),以使bus的“运输量”达到

      最高效率)

      加上为了支持virtual而由内部产生的任何额外负担(overhead)

指针的类型

    “指向不同类型之各指针”间的差异,即不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小;类型为void*的指针因为只持有一个地

    址而不能通过它操作所指之object。由上例可知转换(cast)其实是一种编译器指令,在大部分情况下它并不改变一个指针所含真正地址,它只影响“所指出之内存的大小和其内容”的解释方式。

加上多态之后

    二者都指向基类对象的第一个byte,其间的差别是,派生类指针涵盖的地址包含整个派生类对象,而一个基类指针所涵盖的地址只包含派生类对象的基类子对象部分。

    base class不管是pointer或reference的类型将在编译时期决定以下两点:

      固定的可用接口,也就是说,只能调用derived class的public的接口

      该接口的access level

    当一个base class object被直接初始化为(或被指定为)一个derived class object时。

      derived object就会被切割(sliced)以塞入较小的base type内存中,derived type将没有留下任何蛛丝马迹。多态于是不再呈现,而一个严格的编译器可以在编译器解析一个“通过此object而触发的virtual function调用操作”,因而回避virtual机制。如果virtual

      function被定义为inline,则更有效率上的大收获。

    C++也支持具体的ADT程序风格,如今被称为object-based(OB)。

      一个OB设计可能比一个对等的OO设计速度更快而且空间更紧凑。速度快是因为所有的函数调用操作都在编译时期解析完成,对象构建起来时不需要设置virtual机制。空间紧凑是因为每一个class object不需要负担传统上为了支持virtual机制儿需要的额外负荷。

      不过,OB设计比较没有弹性。
      在弹性(OO)和(OB)之间常常存在着取舍。一个人能够有效选择其一之前,必须先清楚了解两者的行为和应用领域的需求。

    

原文地址:https://www.cnblogs.com/GodZhuan/p/14167887.html