C++的额外成本
类相较于C的struct不仅只包含了数据,同时还包括了对于数据的操作。在语言层面上C++带来了很多面向对象的新特性,类、继承、多态等等。新特性使得C++更加强大,但同时却伴随着空间布局和存取时间的额外成本。作为一个以效率为目标的语言,C++对于面向对象的实现,其实不大,这些额外成本主要由 virtual引起,包括:
- virtual function 机制,用来支持“执行期绑定”。
- virtual base class —虚基类机制,以实现共享虚基类的 subobject。
三种对象模型
C++类包含两种数据成员:静态数据成员和非静态数据成员。同时包含成员函数,静态函数和虚函数三种成员函数,这些机制在C++对象是如何被表现的?
下面有三种模型用以表现它们—简单对象模型、表格驱动对象模型以及C++对象模型。
假定有一个Point类,我们将用三种对象模型来表现它。Point类如下:
classPoint
{
public:
Point(floatxval);
virtual~Point();
floatx()const;
staticintPointCount();
protected:
virtualostream& print(ostream&os)const;
float_x;
staticint_point_count;
};
简单对象模型
简单对象模型:一个C++对象存储了所有指向成员的指针,而成员本身不存储在对象中。也就是说不论数据成员还是成员函数,也不论这个是普通成员函数还是虚函数,它们都存储在对象本身之外,同时对象保存指向它们的指针。
简单对象模型对于编译器来说虽然极尽简单,但同时付出的代价是空间和执行期的效率。显而易见的是对于每一个成员都要额外搭上一个指针大小的空间以及对于每成员的操作都增加了一个间接层。因此C++并没有采用这样一种对象模型,但是被用到了C++中“指向成员的指针”的概念当中。
表格驱动对象模型
表格驱动模型则更绝,它将对象中所有的成员都抽离出来在外建表,而对象本身只存储指向这个表的指针。右图可以看到,它将所有的数据成员抽离出来建成数据成员表,将所有的函数抽取出来建成一张函数成员表,而对象本身只保持一个指向数据成员表的指针。当然C++也没有采用这一种对象模型,但C++却以此模型作为支持虚函数的方案。
C++对象模型
所有的非静态数据成员存储在对象本身中。所有的静态数据成员、成员函数(包括静态与非静态)都置于对象之外。另外,用一张虚函数表(virtual table)存储所有指向虚函数的指针,并在表头附加上一个该类的type_info对象,在对象中则保存一个指向虚函数表的指针。如下图:
三种编程典范
- 程序模型
- ADT模型
- 面向对象模型
一个类的对象的内存大小包括:
- 所有非静态数据成员的大小。
- 由内存对齐而填补的内存大小。
- 为了支持virtual有内部产生的额外负担。
如下类:
classZooAnimal{
public:
ZooAnimal();
virtual~ZooAnimal();
virtualvoidrotate();
protected:
intloc;
Stringname;
};
在 32 位计算机上所占内存为 16 字节: int 四字节, String8 字节(一个表示长度的整形,一个 指向字符串的指针),以及一个指向虚函数表的指针 vptr 。对于继承类则为基类的内存大小 加上本身数据成员的大小。