C++对象模型
一、对象模型
-
简单对象模型
在简单对象模型中,class member并不存放在object中,在object中存放的是一个指向class member的指针;而class function则是存放一个指向function的函数指针
-
表格驱动对象模型
为了对所有class的所有object都有一致的对外表达方式,表格驱动对象模型将所有与member相关的信息抽离出来,放在一个data member table和一个member function table之中,class object本身则存放一个指向这两个表格的指针。
-
C++对象模型
从简单对象模型派生而来,并对内存空间和存取时间做了优化。
在该模型中,Nonstatic data members被配置于每一个class object之内, static data members则被存放在个别的class object之外;Static和nonstatic function members也被存放在个别的class object之外;Virtual function则以两个步骤支持:
- 每个class产生出一堆指向vritual function的指针,放在表格中。这个表格被称为virtual table
vtbl
; - 每个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定
setting
和重置resetting
都由每一个class的constructor、desturctor和copy assignment运算符自动完成。每个class所关联的type_info object也经由virtual table被指出来,通常放在一个slot中。
class Point { public: Point(float xval); virtual ~Point(); protected: virtual ostream& print(ostream &os) const; float x; static int _Point_count; };
- 每个class产生出一堆指向vritual function的指针,放在表格中。这个表格被称为virtual table
继承
C++支持单继承,也支持多继承。
继承关系也可以被指定为虚拟继承virtual
,共享的意思。在虚拟继承的情况下,base class不管在继承串中被派生derived
多少次,永远只会存在一个实例(被称为subobject)。用在菱形继承中。
一个derived class在本质上模塑其base class实例的方法
- 在简单对象模型中,每一个base class可以被derived class object内的一个slot指出,该slot内含base class subobject的地址。这个体制的主要缺点是,因为间接性而导致的空间和存取时间上的额外负担,优点则是class object的大小不会因其base class的改变而受到影响。
- base table模型。base table被产生出来时,表格中的每一个slot内含一个相关的base table地址,这个跟virtual table内含每一个virtual function一样。每一个class object内含一个bptr,它们会被初始化,指向其base class table。这种策略的缺点是:由于间接性而导致的空间和存取时间上的额外负担,优点则是每一个class object中对于继承都有一致的表现方式:每一个class object都应该在某个固定位置上安放一个base table指针,与base class的大小或个数无关。第二个优点是,无需改变class objects本身,就可以放大、缩小、或更改base class table。
- c++最初采用的继承模型并不运用任何间接性:base class subobject 的 data members 被直接放置于 derived class object中。这提供了对base class members最紧凑而且最有xiaol 的存取。但缺点是base class members的任何改变,包括增减、移除、改变类型等都使得所有用到该base class或其derived class的object必须重新编译。
对象模型如何影响程序
不同的对象模型会导致“现有的程序代码必须修改”或“必须加入新的程序代码”。
举个例子
// 在class X中定义了一个copy constructor,一个virtual destructor和一个virtual function foo:
X foobar() {
X xx;
X *px = new X;
// foo() 是一个 virtual function
xx.foo();
px->foo();
delete px;
return xx;
}
// 这个函数有可能在内部被转换为
void foobar(X& _result)
{
// 构造 _result
// _result 用来取代local xx
_result.X::x();
// 拓展X *ptr = new X;
px = _new(sizeof(X));
if (px != 0) px->X::x();
// 扩展xx.foo()但不使用virtual机制
foo(&result);
// 扩展px->foo()使用virtual机制
(*px->vtbl[2])(px);
// 扩展delete px;
if (px != 0)
{
(*px->vtbl[1])(px); // destructor
_delete(px);
}
// 无需使用named return statement
// 无需摧毁local object xx
return;
}
对象的差异
c++程序设计模型直接支持三种programming paradigms
程序设计范式。
- 程序模型 procedural model
- 抽象数据类型模型 abstract data type mode
- 面向对象模型 object-oriented model
多态
Library_materials thing1;
// class Book : public Library_materials { ... }
Book book;
// thing1 并不是一个book
// book被裁切了sliced
// 但是thing1任然保留了Library_material
thing1 = book;
// 调用的是Library_materials
thing1.check_in();
Library_material& thing2;
thing2 = book;
// 引发的的是Book::check_in()
thing2.check_in();
随让可以直接或间接处理继承体系中的一个base class object,但是只有通过pointer或reference的间接处理,才支持OO程序设计所需的多态性质。
在OO paradigm之中,程序员需要处理一个未知实例,它的类型虽然有所界定,却有无穷可能。这组类型受限于其继承体系,然而该体系理论上没有深度和广度的限制。原则上被指定的object的真实类型在每一个特定执行点之前,是无法解析的。
在C++中,只有通过pointers和reference的操作才能够完成。相反地,在ADT paradigm中,程序员处理的是一个拥有固定而单一类型的实例,它在编译时期就已经完全定义好了。
C++以下列方法支持多态:
-
经由一组隐式的转化操作。例如把一个derived class 指针转化为一个指向其public base type的指针。
shape *ps = new circle()
-
仅有
virtual function
机制ps->rotate()
意思是在基类中定义的virtual function
在子类中重写,在通过基类指针指向不同的对象时,可以调用不同的方法实例 -
经由
dynamic_cast
和typeid
运算符if ( circle *pc = dynamic_cast<circle*>(ps))...
多态的主要用途是经由一个共同的接口来影响类的封装,这个接口通常被定义为一个抽象的base class中。这个共享接口是以virtual function
机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实例被调用。
有关dynamic_cast
和typeid
的介绍可以到这篇文章去详细了解一下。
[C++] - dynamic_cast介绍及工作原理、typeid、type_info_dynamic_cast原理-CSDN博客
-
需要多少内存才能够表现一个class object?
一般而言要有:
-
其
nonstatic data members
的总和大小 -
加上任何由于
alignment
的需求而填补padding
上去的空间(可能存在于members之间,也可能存在于集合体边界)alignment
就是将数值调整到某数的倍数。在32位的计算机上,通常alignment
为4bytes,以使bus的“运输量”达到最高效率这句话的意思就是内存对齐。
-
加上为了支持
virtual
而由内部产生的任何额外负担overhead
。
-
指针的类型the type of a pointer
int *p1;
Array<string> *p2;
ZooAnimal *p3;
以内存需求的观点来说,上面三个指针并没有什么不同!它们都需要有足够的内存来放置一个机器地址。“指向不同类型的各指针”之间的差异,既不在其指针的表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容机器大小。
转换cast
之影响”被指出的内存的大小和其内容“的解释方式。
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void rotate();
protected:
int loc;
String name;
};
class Bear : public ZooAnimal {
public:
Bear();
~Bear();
void rotate();
virtual void dance();
protected:
enum Dances {...};
Dances dances_known;
int cell_block;
};
Bear b("Yogi");
Bear *pd = &b;
Bear &rb = *pd;
该类的布局
// 但是如果是通过这种方式来进行的话=
Bear b;
ZooAnimal *pz = &b;
Bear *pd = &b;
它们每个都指向Bear object的第一个bytes。其间的差别是,pd所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中的ZooAnimal subobject。
除了ZooAniamal subobject中出现的members,你不能够使用pz来直接处理Bear的任何members。唯一例外的是通过virtual机制。
z = &b;
Bear *pd = &b;
它们每个都指向Bear object的第一个bytes。其间的差别是,pd所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中的ZooAnimal subobject。
除了ZooAniamal subobject中出现的members,你不能够使用pz来直接处理Bear的任何members。唯一例外的是通过virtual机制。
引用文献:《深度探索C++对象模型》