第一章 关于对象
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)之间常常存在着取舍。一个人能够有效选择其一之前,必须先清楚了解两者的行为和应用领域的需求。