在C语言中,数据和函数是分开声明的,语言本身并不支持数据和函数之间的关联性
例如1
2
3
4
5
6
7
8
9typedef struct point3d {
float x;
float y;
flaot z;
} Point3d;
void Point3d_print {
//print x,y,z
}
而在C++中,则提供了抽象数据模型给予支持1
2
3
4
5class Point3d {
public:
Point3d(float x...) : _x(x),...{}
void print() {//print x,y,z}
}
然而,这时候就会有人问,加上了封装之后,布局的成本增加了多少?
答案是并没有增加成本
class Point3d
支持封装性质并没有给它带来任何空间和执行期的不良后果
不过你即将看到
C++的布局以及存储时间上的主要额外负担都是由virtual
引起的
包括
virtual function
virtual base class
The C++ Object Model
C++中,有两种class data members
:static
, nostatic
有三种class member functions
:static
,nostatic
,virtual
思考如下代码在机器中的表现形式
1 | class Point { |
A Simple Object Model
在这个模型中
一个object就是一系列的slots(指针),每一个slot指向一个members
member本身并不存放在object中,只有指向member的指针才会存放在object中
那么 size(obejct) = size(pointer) * count(member)
A Table-driven Object Model
这个模型则把上面的模型进行了改进
他把member分成data和function,然后分别放在data memeber table和member function table中
object则存放指向这两个table的指针,data member table则直接持有data,而member function table则存放着一系列的slots,每一个slot指出一个member function
虽然这个模型没有实际应用到C++编译器中
但是member function table这种观念成为支持virtual function的一个有效方案
The C++ Object Model
在此模型中
non-static data member被配置到每一个object内,static data members则被存放在object之外
static 和 non-static function member也被存放在object之外
virtual functions则通过以下两个步骤来支持
1.每一个class产生一堆指向virtual的指针,放在表格中(vtbl)
object2.每一个object被安插一个指针(vptr),指向相关的vtbl,vtpr的设定和重置都由每一个class的constructor, destructor和copy assignment运算符自动完成,每一个class所关联的type_info object也经由vtbl被指出来,通常被放在表格的第一个solt
加上继承
在虚拟继承的情况下,base class不管在继承中被派生多少次,永远只存在一个实例
那么base table应该是怎么样的呢?
首先根据simple object model
,我们可以在class object
中放置slot
指向base class
另外一种机制是,每一个class object
都会被安放一个base table pointer
然后继承多个base class
只需要改变base table
而无需改变class object
但是无论哪一种机制,间接性的级数都会因为继承的深度而增加
例如以下这种继承链
Library_materials—->Book—->Rental_Book
Rental_Book需要两次间接存储才能取到Library_materials
的members
对象的差异
只有通过Pointer和Reference的间接处理,才能支持OO程序设计中所需的多态性质
在C++中,多态只存于一个个的public class体系中,并以如下的方式支持多态
隐式转换,例如dervied class指针转换成base class指针
virtual function机制
经由dynamic_cast和typeid运算符
然后,可以试着思考,需要多少内存才能表现一个class object?
nonstatic data members的总大小
加上由alignment的需求而填补上去的空间(alignment就是将数值调整到某数的倍数,32位机器alignment为4 bytes,可以使bus“运输量”达到最高效率
为了支持virtual而产生的额外负担(vptr)
指针的类型
一个指针,不管指向的类型是什么,指针本身所需的内存大小是固定的
但是一个指向整数的指针和一个指向template Array的指针有什么不同呢?
首先,肯定的是,指针的内存需求是一样的
其次,它们之间的差异就在于其所寻址出来的object类型不同
换句话说,就是“指针类型”会教导编译器如何解释某个特定地址中的内存内容和大小
- 指向地址1000的整数指针,在32位机器上,将涵盖地址空间1000-1003
假设类ZooAnimal1
2
3
4
5
6
7
8
9class ZooAnimal {
public:
zooAnimal();
virtual ~ZooAnimal();
virtual void rotate();
protected:
int loc;
string name;
};
- 一个指向ZooAnimal的指针将跨越地址空间1000-1015(int-4 + string(int+char)-8 + vptr-4)
那么一个指向1000的void *的指针将涵盖怎么样的内存空间呢?
我们并不知道,这也就是为什么void指针能够持有地址却无法操作其指向的object的缘故
加上多态之后
1 | class Bear : public ZooAnimal {...}; |
Q1:为什么za的vptr不指向Bear的vtbl?
Q2:为什么rotate调用的是ZooAnimal::rotate()?
A1:如果某个object含有一个或者一个以上的vptrs,那些vptrs不会被用于初始化的source object所改变
A2:我们知道,pointer或者reference之所以支持多态,是因为它们可以改变所指向内存的大小和内容的解释方式
而直接进行dervied class object对base class object的赋值操作,因为object所占用的内存大小不一样
因此会对dervied class object进行对象切割以塞入更小的base class object中
于是乎dervied type的成分就会被完全切除,多态也无从表现