前言
“本书是由一位编译器设计者针对中高级C++程序员所写的。隐藏在这本书背后的假设是,程序员如果了解C++对象模型,就可以写出比较没有错误倾向而且比较有效率的代码。”
正是因为在实际编程中吃了亏,所以才觉得读这本书是十分有必要的,比如之前的一篇博文:
http://blog.csdn.net/jiange_zh/article/details/51871782
如果读过这本书,想必也就不会有所困惑了。
下面是我个人的一些读书笔记,大部分知识可能是从书中摘录下来的,仅方便日后自己快速回顾查阅。
第1章 关于对象(Object Lessons)
加上封装后的布局成本
C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:
virtual function机制——用以支持一个有效率的“执行期绑定”(runtime binding);
virtual base class——用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实体”。
此外,还有一些多重继承下的额外负担,发生在“一个derived class和其第二或后继base class的转换”之间。
C++对象模型(The C++ Object Model)
在C++中, 有两种class data members: static 和 nonstatic;
以及三种class member functions: static、nonstaitc 和 virtual。
已知有以下 class Point 声明:
class Point
{
public:
Point( float xval );
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream& print( ostream &os ) const;
float _x;
static int _point_count;
};
这个class在机器中将会被如何表现呢?
Nonstatic data members 被配置于每一个 class object 之内;
Static data members 被存放在所有的 class object 之外;
Static 和 nonstatic function members 也被存放在所有的 class object 之外;
Virtual functions 则以两个步骤支持之:
1)每一个 class 产生出一堆指向 virtual functions 的指针,放在表格之中,这个表格被称为virtual table(vtbl);
2)每一个 class object 被添加了一个指针,指向相关的 virtual table,通常该指针被称为vptr。vptr的设定和重置都由每一个 class 的 constructor、destructor 和 copy assignment 运算符自动完成。每一个class所关联的 type_info object (用以支持 runtime type identification,RTTI)也经由 virutal table 被指出来,通常是放在表格的第一个slot处。
一个 class object 的内存大小
一般而言,要有:
1. 其 nonstatic data members 的大小总和;
2. 加上由内存对齐而填补的内存大小;(关于补齐,在《深入理解计算机系统》一书中有所描述)
3. 加上为了支持virtual而由内部产生的任何额外负担。
一个指针,不管它指向哪一种数据类型,指针本身的内存大小是固定的。
但是,一个指向类Test的指针是如何与一个指向整数的指针有所不同呢?
——从内存需求的角度来看,并没有什么不同,它们都需要足够的内存来存放一个机器地址。
它们的不同之处在于,其寻址处理的object类型不同,也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。
对于 void* 指针,由于它没有特定的类型,所以编译器并不知道如何去解释,这就是为什么一个类型为 void* 的指针只能够存放一个地址,但是不能够通过它操作所指之 object 的缘故。
对于转型(cast),它其实是一种编译器指令,大部分情况下并不改变一个指针所含的真正地址,它只影响“被指之物内存的大小和其内容”的解释方式。
加上多态之后
假设有一个基类ZooAnimal(16bytes),以及一个继承自ZooAnimal的类Bear(8bytes)。
Bear b("Yogi");
Bear *pb = &b;
Bear &rb = *pb;
b、pb、rb的内存需求:
不管是pointer还是reference(事实上由指针来实现的)都只需要一个word的空间(32位机器上是4bytes)。Bear object需要24bytes,包括ZooAnimal的16bytes和Bear所带来的8bytes。
假设我们的Bear object 放在地址1000处,一个Bear指针和一个ZooAnimal指针有什么不同?
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
它们都指向Bear object的第一个byte,差别是,pb所涵盖的地址范围是整个Bear object,而pz所涵盖的地址只包含Bear object中的ZooAnimal成分。
除了ZooAnimal成分中出现的members,你不能够使用pz来直接处理Bear的任何members。唯一例外是通过virtual机制。
一个pointer或一个reference之所以支持多态,是因为它们并不引发内存中任何与类型有关的内存委托操作,会受到影响的只是它们所指向的内存的“大小和内容解释方式”而已。