关于对象
1. c++ 在布局以及存取时间上主要的额外负担是有virtual引起,包括:
virtual function机制:用以支持一个有效率的“执行期绑定” (runtime binding);
virtual base class:用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实体”。
2. c++中,有两种 class data members: static和nonstatic,以及三种class member functions: static, nonstatic 和virtual。
3. c++对象模型:
此模型中,nonstatic data members被配置在每一个class object之内,static data members则被存放在所有的class object之外。 static和弄static 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)也经由virtual table被指出来,通常放在表格的第一个slot处。
4. 在虚拟继承的情况下,base class不管在继承串中被派生多少次,永远只会存在一个实体。
virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针。
5. C++程序设计模型直接支持三种程序设计典范
(1)程序模型,如字符串处理
char boy[] = "Danny"; char* p_son; p_son = new char[strlen(boy) + 1]; strcpy(p_son, boy); if (!strcmp(p_son, boy)) take_to_sidneyland(boy);
(2)抽象数据类型模型
String girl = "Anna"; String daughter; // String::operator=(); daughter = girl; // String::operator==(); if (girl == daughter) take_to_disneyland(girl);
(3)面向对象模型
6. C++以下列方法支持多态:
(1)经由一组隐含的转化操作。例如把一个derived clas指针转化为一个指向其public base type的指针:
shape* ps = new Circle();
(2)经由virtual function机制:
ps->rotate();
(3)经由dynamic_cast和typeid运算符:
if (circle* pc = dynamic_cast<circle*>(ps))
7. 一个class object需要多少内存:
(1)其nonstatic data members的总和大小;
(2)加上任何由于alignment的需求而填补上去的空间;
(3)加上为了支持virtual而由内部产生的任何额外负担。
8. 指针的类型
指向不同类型之各指针间的差异,既不在其指针表示法不同,也不在其内容不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。
一个指向地址1000的整数指针,在32位机器上,将涵盖地址空间1000~1003;那么一个指向地址1000而类型为void*的指针,将涵盖怎样的地址空间呢?这并不能知道。这就是一个类型为void*的指针只能够含有一个地址,而不能够通过它操作所指之object的缘故。
所以,转型(cast)其实是一种编译器指令。大部分情况下它并不能改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式。
9. 加上多态
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* pb = &b; Bear& rb = *pb;
b, pb, rb有怎样的内存布局?不管是pointer或是reference都只需要一个word的空间。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 subobject。
除了ZooAnimal subobject中出现的members,你不能够偶使用pz来直接处理Bear任何members。唯一例外是通过virtual机制。
pz->cell_block; // 不合法:cell_block不是ZooAnimal的一个成员 ((Bear*)pz)->cell_block; // ok: 经过一个downcast操作就没有问题 pb->cell_block; // ok: cell_block是Bear的一个成员
当我们写 pz->rotate();时,pz的类型将在编译时期决定一下两点:
(1)固定的可用接口,pz只能够调用ZooAnimal的public接口;
(2)该接口的access level。
在每一个执行点,pz所指的object类型可以决定rotate()所调用的实体,类型信息的封装并不是维护于pz之中,而是维护于link之中,此link存在于object的vptr和vptr所指之virtual table之间。
Bear b; ZooAnimal za = b; // 这会引起切割(sliced) za.rotate(); // 调用ZooAnimal::rotate()
为什么rotate()所调用的是ZooAnimal实体而不是Bear实体?此外,如果初始化函数将一个object内容完整拷贝到另一个object中去,为什么za的vptr不指向Bear的virtual table?
编译器在初始化和指定(assignment)操作之间做出了仲裁。编译器必须确保如果某个object含有一个或一个以上的vptrs,那些vptrs的内容不会被base class object初始化或改变。
za并不是一个Bear,它是一个ZooAnimal。多态所造成的一个以上的类型的潜在力量,并不能实际发挥在直接存取objects这件事情上。
一个pointer或reference之所以支持多态,是因为它们并不引发内存中任何”与类型有关的内存委托操作(type-dependent commitment)“;会受到改变的只是它们所指向的内存的”大小和内容解释方式“而已。